# 浮動小数点数の補足
- 教科書3.4節。

## 丸め誤差
```{figure} ./figs/float1.svg
:name: float1
丸め誤差
```

コンピュータは0,1の2進数ですべての情報を処理するため、少数点数を厳密に表現することは難しい。できなくはないが、プログラミング言語や書き方の工夫が必要であり、一般的には「このぐらいの精度で表現できていれば一般用途上問題ない」ように処理していることが多い。

2進数を扱うことに加えて、その副次的結果としてコンピュータでは少数点数を表現するために **{index}`浮動小数点数 <ふどうしょうすうてんすう-浮動小数点数>`({index}`floating point numbers`)** と呼ばれる方式を採用している。浮動小数点数では、数値を **{index}`仮数部<かすうぶ-仮数部>`, {index}`基数<きすう-奇数>`, {index}`指数部<しすうぶ-指数部>`** の組み合わせで $仮数部 \times 基数^{指数部}$ として表現する。例えば、0.5を10進数の浮動小数点数で表すと $5.0 \times 10^{-1}$ となる。元の値は0.5なのに対し、浮動小数点数では小数点の位置がズレている。このように小数点の位置が動くため「浮動小数点」数と呼ばれている。

```{tip}
- 関連
  - [15. 浮動小数点演算、その問題と制限](https://docs.python.org/ja/3/tutorial/floatingpoint.html)
  - [Pythonで浮動小数点数floatの誤差を考慮して比較](https://note.nkmk.me/python-math-isclose/)
  - [【5分で覚えるIT基礎の基礎】ゼロから学ぶ2進数　第4回](https://xtech.nikkei.com/it/members/ITPro/ITBASIC/20020624/1/?rt=nocnt)
  - [Wikipedia: 浮動小数点数](https://ja.wikipedia.org/wiki/浮動小数点数)
  - [【浮動小数点数】をわかりやすく説明](https://it-lab.com/column/floating-point-number/)
```

ここでは浮動小数点数を扱う際の問題点について紹介する。1つ目が{index}`丸め誤差<まるめごさ-丸め誤差>`だ。
これは、小数点のある数は必ずしも厳密には表現できない（ことがある）ことを指している。スライド内のコード例では0.1を10回足しており、その結果は 1.0 になることを期待する。しかし 1.0 と等しいかどうかを確認すると False となり、print出力すると 0.99999,,, という数字が出力されてしまっている。

---
## 丸め誤差を踏まえた数値演算
浮動小数点数には丸め誤差が起こりうるため、「N回足した値が1.0と等しければ」というように特定の値と等しいことを条件式として書いてしまうと不具合を起こしてしまうことが **極めて多い**。例えば以下のコードを実行してみよう。気持ちとしては変数 sum が1.0になった時点でループを抜けてほしいが、実際には抜けること無く最後までループを繰り返してしまう。

```python
sum = 0
for i in range(20):
    sum += 0.1
    if sum == 1.0:
        print(sum)
        break
    else:
        print(sum)
```

この問題を避けるため、浮動小数点数を用いた条件式は0に近いある小さな数値を設定した上で「特定の範囲内に入れば」のように指定することが多い。例えば上記の例は以下のように記述する。数学用語としての[{index}`epsilon`（{index}`イプシロン<いぷしろん-イプシロン>`）](https://ja.wikipedia.org/wiki/Ε)は「非常に小さな数」を表す記号として用いることが多い。``1e-5`` は **{index}`指数表記<しすうひょうき-指数表記>`** であり ``1.0 * 10**(-5) == 0.00001`` である。この小さな数値を用いて $特定の値 \pm epsion$ で下限と上限を設けることで範囲を設定し、その範囲内にいることを「特定の値に十分等しい」と看做すようにコードを書く。

```python
epsilon = 1e-5
sum = 0
for i in range(20):
    sum += 0.1
    if 1.0 - epsilon < sum < 1 + epsilon:
        print(sum)
        break
    else:
        print(sum)
```

```{tip}
上記に近い実装が[{index}`math.isclose関数`](https://docs.python.org/ja/3.8/library/math.html#math.isclose)として提供されています。もしこのような「丸め誤差を踏まえた近似処理」ではなく、正確な10進数表現が必要な場合には[{index}`decimalモジュール`](https://docs.python.org/ja/3/library/decimal.html#module-decimal), [{index}`fractionsモジュール`](https://docs.python.org/ja/3/library/fractions.html#module-fractions)の利用を検討してみてください。
```

---
## オーバーフロー
```{figure} ./figs/float2.svg
:name: float2
オーバーフロー
```

浮動小数点数2つ目の問題点は **{index}`オーバーフロー<おーばーふろー-オーバーフロー>`（{index}`桁あふれ<けたあふれ-桁あふれ>`）** と呼ばれている。浮動小数点形式は固定小数点形式と比べると幅広い数値を扱うことが可能だが、それでも大きすぎる数字と小さすぎる数字を同時に扱うことは困難である。

図では2つの例を示している。1つ目の例は「扱うことすらできないケース」であり、数値として保存することもできず **{index}`OverflowError`** と返されている。2つ目の例は、1つ目よりは小さな数値（100桁＋少数点数）を扱っているが、演算結果は想定と大きく異なっている。たかだか0.1を足しただけなのに、結果はそうなっていない。これは誤差と捉えても良いだろうか。

一般的には問題にならないことが多いが、このようなケースが起こりうることを認識しておこう。
