0.1の浮動小数点数は0.1より大きいのに、10回足すと1.0より小さいのはなぜか【後編】

# 前編
この記事の続きです。

[0.1の浮動小数点数は0.1より大きいのに、10回足すと1.0より小さいのはなぜか【前編】](https://qiita.com/yucatio/items/85a8e8d381d300092295)

# (前編からのコピー)0.1を浮動小数点数で表すと

`0.1`を浮動小数点数で表したときの符号、指数、仮数は以下です。

```
符号 = 0
指数 = 01111111011
仮数 = 1001100110011001100110011001100110011001100110011010
```

指数を10進数で表すと`1019`です。浮動小数点形式の指数は`1023`が足されていますので、これを引くと`-4`になります。


# (前編からのコピー)0.1を足していったときの結果とどのくらい足されているか
ヘッダを除いた奇数行に``0.1``を足していったときの計算結果、偶数行にそれぞれの差分は以下の表に示します。

期待値| 計算結果/差分
---|:---
0.1|`0.1000000000000000055511151231257827021181583404541015625`
 |`0.1000000000000000055511151231257827021181583404541015625`
0.2|`0.200000000000000011102230246251565404236316680908203125`
 |`0.100000000000000033306690738754696212708950042724609375`
0.3|`0.3000000000000000444089209850062616169452667236328125`
 |`0.09999999999999997779553950749686919152736663818359375`
0.4|`0.40000000000000002220446049250313080847263336181640625`
 |`0.09999999999999997779553950749686919152736663818359375`
0.5|`0.5`
 |`0.09999999999999997779553950749686919152736663818359375`
0.6|`0.59999999999999997779553950749686919152736663818359375`
 |`0.09999999999999997779553950749686919152736663818359375`
0.7|`0.6999999999999999555910790149937383830547332763671875`
 |`0.09999999999999997779553950749686919152736663818359375`
0.8|`0.79999999999999993338661852249060757458209991455078125`
 |`0.09999999999999997779553950749686919152736663818359375`
0.9|`0.899999999999999911182158029987476766109466552734375`
 |`0.09999999999999997779553950749686919152736663818359375`
1.0|`0.99999999999999988897769753748434595763683319091796875`

# (前編からのコピー)浮動小数点数の足し算の手順
正の浮動小数点数同士の足し算は以下の手順で行われます。

① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
③ 仮数を足し合わせる
④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)

#  (前編からのコピー)保護桁とスティッキビット
保護桁(Guard digits)はあふれてしまった桁を保存する領域です。
スティッキビット(Sticky bit)は保護桁以下の数に1以上の数が含まれているかどうかを表す真偽値です。

前編で、正の数の加算では保護桁は1桁で十分という話が出ましたが、負の数まで拡張する場合は2桁必要です。
以下では保護桁2桁とスティッキッビットを使用します。保護桁の2桁(2bit)には、上からガードビット(Guard bit)、ラウンドビット(Round bit)という名前がついています。

# 0.1を10回足したときの計算過程

`0.1`を足したときの計算過程を順を追って確かめます。

## 今回使用する表現

今回は以下のような図を用いて説明を行います。

![f_legend.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/00b3d046-bae9-46c5-4859-6d3e650e088e.png)

番号|列名|説明
---|---|---
①|概算|どのような計算を行なっているのか大まかに記載しています。
②|指数(十進数)|浮動小数点数で表された指数を、10進数に変換し、1023を引いたものです。
③|繰り上がり|足し算した結果、桁が繰り上がることがあるのでそのぶんの領域を確保しています。
④|整数部|浮動小数点形式では整数部は常に`1`です。
⑤|0から52|仮数です。52桁全部を表示するとみにくくなるため、上位8桁と下位8桁のみ表示しています。
⑥|ガード・ラウンド・スティッキ|上で説明した保護桁とスティッキビットです。

## 0.1 + 0.1 の計算過程
``0.1 + 0.1``の計算過程を確かめます。
![f0_1_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/e84756bc-e358-b066-9d1a-facb418776ef.png)

① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
指数が同じなので何もしません。

③ 仮数を足し合わせる
足し合わせた結果です
![f0_1_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/99b9ce07-7ba1-9337-c450-d70005850627.png)

④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げが発生しているので、指数に`1`を足し、仮数を`2`で割ります。2進数なので、2で割ると右に桁が1つずれます(ビットシフト)。
![f0_1_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/3d5846b1-2364-6e93-5bdd-ddd19d8633a6.png)


⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれたビットは`0`のみなので、切り捨てます。
![f0_1_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/24c37c2c-4bfb-f6ff-c54c-d71010367a40.png)



以上で、

```
指数 = -3 (10進数)
仮数 = 1001100110011001100110011001100110011001100110011010 (2進数)
```

が得られました。これを10進数に変換すると、

```
0.200000000000000011102230246251565404236316680908203125
```

です。

## 0.2(累積和) + 0.1 の計算過程
上で得られた値にさらに``0.1``を足します。
![f0_2_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/b9b5139b-6f32-551e-b00d-249bcd969b57.png)


① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
`0.2`の方が指数が`1`大きいので、`0.1`の指数に`1`を足し、仮数を`1`つ右にずらします
![f0_2_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/ff55ed5e-599a-5ce1-2bc5-1bd35d7660a7.png)


③ 仮数を足し合わせる
足し合わせた結果です。
![f0_2_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/7078a361-dc01-d5e1-1301-dfd10b48735d.png)


④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げが発生しているので、指数に`1`を足し、仮数を右に`1`つずらします。
![f0_2_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/9b2c94fe-df7d-3fbe-6c05-32caf675587b.png)


⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は`10`で、半分ちょうどなので、偶数方向に丸めます。今回は仮数の最下位ビットが`1`なので、切り上げます。
![f0_2_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/c0db1bf0-f8f9-0a56-1ea0-ee45cf353036.png)


以上で、

```
指数 = -2 (10進数)
仮数 = 11001100110011001100110011001100110011001100110100 (2進数)
```

が得られました。これを10進数に変換すると、

```
0.3000000000000000444089209850062616169452667236328125
```
です。

``0.2(累積和) + 0.1``では、もともと``0.1``が浮動小数点数では``0.1``より大きいことと、足した結果をさらに切り上げていることで、``print``したときに期待したのとは違う結果が表示されます。

```python
print(0.1 + 0.1 + 0.1)
# => 0.30000000000000004
```

## 0.3(累積和) + 0.1 の計算過程
上で得られた値にさらに``0.1``を足します。
![f0_3_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/1b35d8ec-5656-7ed4-fe6f-6dd0c90f0edc.png)


① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
`0.3`の方が指数が`2`大きいので、`0.1`の指数に`2`を足し、仮数を`2`つ右にずらします。
![f0_3_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/43cf0867-cbdd-d200-1a70-d53dad6cde82.png)


③ 仮数を足し合わせる
足し合わせた結果です。
![f0_3_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/e200ae36-9ff1-5ff8-0420-ef38f588f042.png)


④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げは発生しなかったので、何もしません

⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は`10`で、半分ちょうどなので、偶数方向に丸めます。今回は仮数の最下位ビットが0なので、切り捨てます。
![f0_3_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/16e2a551-3de0-7ae8-d8b4-ff0b6813fde8.png)


以上で、

```
指数 = -2 (10進数)
仮数 = 1001100110011001100110011001100110011001100110011010 (2進数)
```

が得られました。これを10進数に変換すると、

```
0.40000000000000002220446049250313080847263336181640625
```

です。

## 0.4(累積和) + 0.1 の計算過程
上で得られた値にさらに`0.1`を足します。
![f0_4_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/8e3581bb-2c60-70ad-6716-0fd4b7b7eafa.png)


① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
`0.4`の方が指数が`2`大きいので、`0.1`の指数に`2`を足し、仮数を`2`つ右にずらします。
![f0_4_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/79f2817f-eb2f-c161-54fc-c6e8e4171929.png)


③ 仮数を足し合わせる
足し合わせた結果です。仮数部のすべての値が`0`になりました。
![f0_4_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/56397e36-cd65-6a83-7409-83f31a141399.png)


④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げが発生しているので、指数に`1`を足し、仮数を`1`つ右にずらします。
![f0_4_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/06855091-2f67-952a-8aff-5c4c23fff684.png)


⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は`010`なので切り捨てられます。
![f0_4_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/2d686e98-2c0b-a705-ad92-c62abe540682.png)


以上で、

```
指数 = -1 (10進数)
仮数 = 0000000000000000000000000000000000000000000000000000 (2進数)
```

が得られました。これを10進数に直すと、

```
0.5
```

です。ぴったりになったのは偶然です。

## 0.5(累積和) + 0.1 の計算過程
上で得られた値にさらに`0.1`を足します。
![f0_5_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/d72b5d89-06f7-f8a8-b8bd-aca06399d84c.png)


① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
`0.5`の方が指数が`3`大きいので、`0.1`の指数に`3`を足し、仮数を`3`つ右にずらします。
![f0_5_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/04106ca4-e717-df7b-9db9-d09ae886139f.png)


③ 仮数を足し合わせる
足し合わせた結果です。
![f0_5_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/467743f8-7914-c74f-fb49-d60fb119ea29.png)


④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げは発生しなかったので、何もしません

⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は`010`なので切り捨てられます。
![f0_5_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/03cc5c7b-c338-95d5-b1c8-77ca45b53721.png)


以上で、

```
指数 = -1 (10進数)
仮数 = 11001100110011001100110011001100110011001100110011 (2進数)
```

が得られました。これを10進数に直すと、

```
0.59999999999999997779553950749686919152736663818359375
```

です。

## 0.6(累積和) + 0.1、0.7(累積和) + 0.1、0.8(累積和) + 0.1の計算過程
`0.6`から`0.8`までは`0.5(累積和) + 0.1`とほぼ同様なので省略します。値の切り捨てが発生するので、この範囲では`1`より小さい数が足されています。

## 0.9(累積和) + 0.1 の計算過程
こちらも`0.5(累積和) + 0.1`とほぼ同様ですが、結果がどのようになるかみてみましょう。
![f0_9_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/1b3961da-c0b4-3098-4304-f6991b0c81da.png)


① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
`0.9`の方が指数が`3`大きいので、`0.1`の指数に`3`を足し、仮数を`3`つ右にずらします。
![f0_9_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/1e97259c-6569-b3a1-2c83-3c563a925725.png)


③ 仮数を足し合わせる
足し合わせた結果です。
![f0_9_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/03bd6770-88c5-9cc3-38fa-3925757c1a4a.png)


④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げは発生しなかったので、何もしません

⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は`010`なので切り捨てられます。
![f0_9_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288329/cc18a895-ebb5-260c-83ab-8117d18debe9.png)


以上で、

```
指数 = -1 (10進数)
仮数 = 1111111111111111111111111111111111111111111111111111 (2進数)
```

が得られました。これを10進数に直すと、

```
0.99999999999999988897769753748434595763683319091796875
```

です。

ここまでで、``0.1``を10回足したときの動作を確認することができました。

# まとめ
`0.1`を浮動小数点数にすると`0.1`よりわずかに大きくなるのに、10回足すと`1.0`より小さいのはなぜか、の答えは、
2つの浮動小数点数を足す際に、仮数に入りきらない数を丸めるから、でした。


# 参考資料

- [浮動小数点演算について](https://docs.oracle.com/cd/E19957-01/806-4847/ncg_goldberg.html)
- [3.6 Floating Point(PDF)](http://www-ise2.ist.osaka-u.ac.jp/~katou/pdf/p209.pdf)
- [Why you need a Guard bit, in addition to the Round and Sticky bits(PDF)](http://pages.cs.wisc.edu/~david/courses/cs552/S12/handouts/guardbits.pdf)
- [浮動小数点の丸めの方向と性質 (1)](http://math-koshimizu.hatenablog.jp/entry/2016/12/04/224208)
- [decimal --- 十進固定及び浮動小数点数の算術演算](https://docs.python.org/ja/3/library/decimal.html)

# 使用したツールなど

- [浮動小数点数内部表現シミュレーター](https://tools.m-bsys.com/calculators/ieee754.php)
- [2進数、8進数、10進数、16進数相互変換ツール](https://hogehoge.tk/tool/number.html)