前編
この記事の続きです。
0.1の浮動小数点数は0.1より大きいのに、10回足すと1.0より小さいのはなぜか【前編】
(前編からのコピー)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
を足したときの計算過程を順を追って確かめます。
今回使用する表現
今回は以下のような図を用いて説明を行います。
番号 | 列名 | 説明 |
---|---|---|
① | 概算 | どのような計算を行なっているのか大まかに記載しています。 |
② | 指数(十進数) | 浮動小数点数で表された指数を、10進数に変換し、1023を引いたものです。 |
③ | 繰り上がり | 足し算した結果、桁が繰り上がることがあるのでそのぶんの領域を確保しています。 |
④ | 整数部 | 浮動小数点形式では整数部は常に1 です。 |
⑤ | 0から52 | 仮数です。52桁全部を表示するとみにくくなるため、上位8桁と下位8桁のみ表示しています。 |
⑥ | ガード・ラウンド・スティッキ | 上で説明した保護桁とスティッキビットです。 |
0.1 + 0.1 の計算過程
① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
指数が同じなので何もしません。
④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げが発生しているので、指数に1
を足し、仮数を2
で割ります。2進数なので、2で割ると右に桁が1つずれます(ビットシフト)。
⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれたビットは0
のみなので、切り捨てます。
以上で、
指数 = -3 (10進数)
仮数 = 1001100110011001100110011001100110011001100110011010 (2進数)
が得られました。これを10進数に変換すると、
0.200000000000000011102230246251565404236316680908203125
です。
0.2(累積和) + 0.1 の計算過程
① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
0.2
の方が指数が1
大きいので、0.1
の指数に1
を足し、仮数を1
つ右にずらします
④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げが発生しているので、指数に1
を足し、仮数を右に1
つずらします。
⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は10
で、半分ちょうどなので、偶数方向に丸めます。今回は仮数の最下位ビットが1
なので、切り上げます。
以上で、
指数 = -2 (10進数)
仮数 = 11001100110011001100110011001100110011001100110100 (2進数)
が得られました。これを10進数に変換すると、
0.3000000000000000444089209850062616169452667236328125
です。
0.2(累積和) + 0.1
では、もともと0.1
が浮動小数点数では0.1
より大きいことと、足した結果をさらに切り上げていることで、print
したときに期待したのとは違う結果が表示されます。
print(0.1 + 0.1 + 0.1)
# => 0.30000000000000004
0.3(累積和) + 0.1 の計算過程
① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
0.3
の方が指数が2
大きいので、0.1
の指数に2
を足し、仮数を2
つ右にずらします。
④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げは発生しなかったので、何もしません
⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は10
で、半分ちょうどなので、偶数方向に丸めます。今回は仮数の最下位ビットが0なので、切り捨てます。
以上で、
指数 = -2 (10進数)
仮数 = 1001100110011001100110011001100110011001100110011010 (2進数)
が得られました。これを10進数に変換すると、
0.40000000000000002220446049250313080847263336181640625
です。
0.4(累積和) + 0.1 の計算過程
① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
0.4
の方が指数が2
大きいので、0.1
の指数に2
を足し、仮数を2
つ右にずらします。
③ 仮数を足し合わせる
足し合わせた結果です。仮数部のすべての値が0
になりました。
④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げが発生しているので、指数に1
を足し、仮数を1
つ右にずらします。
⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は010
なので切り捨てられます。
以上で、
指数 = -1 (10進数)
仮数 = 0000000000000000000000000000000000000000000000000000 (2進数)
が得られました。これを10進数に直すと、
0.5
です。ぴったりになったのは偶然です。
0.5(累積和) + 0.1 の計算過程
① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
0.5
の方が指数が3
大きいので、0.1
の指数に3
を足し、仮数を3
つ右にずらします。
④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げは発生しなかったので、何もしません
⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は010
なので切り捨てられます。
以上で、
指数 = -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
とほぼ同様ですが、結果がどのようになるかみてみましょう。
① 指数が小さい方を指数が大きい方に合わせる
② 指数を大きくしたぶん、仮数を小さくして調整する
0.9
の方が指数が3
大きいので、0.1
の指数に3
を足し、仮数を3
つ右にずらします。
④ 繰り上げが発生した場合は、指数に1を足しそのぶん仮数を小さくする
繰り上げは発生しなかったので、何もしません
⑤ 仮数のあふれてしまった桁を丸める(偶数丸め)
あふれてしまった桁は010
なので切り捨てられます。
以上で、
指数 = -1 (10進数)
仮数 = 1111111111111111111111111111111111111111111111111111 (2進数)
が得られました。これを10進数に直すと、
0.99999999999999988897769753748434595763683319091796875
です。
ここまでで、0.1
を10回足したときの動作を確認することができました。
まとめ
0.1
を浮動小数点数にすると0.1
よりわずかに大きくなるのに、10回足すと1.0
より小さいのはなぜか、の答えは、
2つの浮動小数点数を足す際に、仮数に入りきらない数を丸めるから、でした。
参考資料
- 浮動小数点演算について
- 3.6 Floating Point(PDF)
- Why you need a Guard bit, in addition to the Round and Sticky bits(PDF)
- 浮動小数点の丸めの方向と性質 (1)
- decimal --- 十進固定及び浮動小数点数の算術演算