0.1 + 0.2が0.3にならない理由

古典的な浮動小数点パズルの解説。IEEE 754で0.1 + 0.2が0.30000000000000004を生成する理由と、浮動小数点比較を正しく処理する方法を学びます。

Precision

Decimal Value

0.30000000000000004

Float32 Hex

0x3E99999A

Float64 Hex

0x3FD3333333333334

詳細な説明

0.1 + 0.2 !== 0.3という式は、最も有名な浮動小数点の驚きです。0.1、0.2、0.3はバイナリ浮動小数点で正確に表現できないため、事実上すべてのプログラミング言語で発生します。

0.1がバイナリで正確でない理由:

10進数の0.1はバイナリでは0.0001100110011...です — 10進数で1/3 = 0.333...と同様に無限に繰り返すパターンです。IEEE 754はこれを最も近い表現可能な値に丸める必要があります。

0.1に最も近いfloat64は: 0.1000000000000000055511151231257827021181583404541015625

0.2に最も近いfloat64は: 0.200000000000000011102230246251565404236316680908203125

加算:

格納された値を加算すると: 0.10000000000000000555... + 0.20000000000000001110... = 0.30000000000000004440...

この結果に最も近い表現可能なfloat64は: 0.3000000000000000444089209850062616169452667236328125

しかし0.3に最も近い表現可能なfloat64は: 0.299999999999999988897769753748434595763683319091796875

これらは異なる値なので、0.1 + 0.2 !== 0.3となります。

ビットレベルの表示:

Float64 Hex
0.1 0x3FB999999999999A
0.2 0x3FC999999999999A
0.1+0.2 0x3FD3333333333334
0.3 0x3FD3333333333333

0.1+0.2と0.3は最後の16進数桁が異なることに注目: 4 vs. 3。

浮動小数点の正しい比較方法:

  1. イプシロン比較: 1.0付近の値にはMath.abs(a - b) < Number.EPSILON
  2. 相対イプシロン: Math.abs(a - b) < Math.max(Math.abs(a), Math.abs(b)) * tolerance
  3. 固定小数点: 通貨は100倍して(ドルでなくセントで計算)
  4. 10進数ライブラリ: 正確な10進算術にはdecimal.jsBigDecimalを使用

これはバグではありません:

この動作はIEEE 754に従った正しい結果です。10進法の分数を2進法で表現することに固有の問題です。同じ問題はC、C++、Java、Python、Rust、Goなど、IEEE 754浮動小数点を使用するすべての言語に存在します。

ユースケース

すべての開発者が、正しい数値比較を書き、丸め誤差のない金融計算を実装し、浮動小数点精度の問題をチームメイトやステークホルダーに説明するために、この動作を理解する必要があります。

試してみる — IEEE 754 Inspector

フルツールを開く