Rubyのirbを開いて0.1 + 0.2と入力すると、0.3ではなく0.30000000000000004が返ってくる。開発者のまつもとゆきひろ氏が日本で生み出したRubyであっても例外ではない——これは特定の言語のバグではなく、IEEE 754標準が分数を2進法でどう表現するかの直接的な結果であり、JavaScriptやPython、Javaなど同じ標準を使うほぼすべての言語で同じことが起きる。
浮動小数点数の3つの部分
IEEE 754に基づく数値は、符号(正か負か)、仮数(数値の有効桁)、指数(小数点をシフトする累乗)の3つの要素として保存されます。これは科学的記数法に似ています:1.5 × 10²、ただし2進法です。
なぜ0.1は2進法で正確に表現できないのか
10進法の0.1は2進法では無限小数です(10進法の1/3が無限小数であるのと同様です)。コンピュータは限られた数の仮数ビットしか保存できないため、数値は最も近い表現可能な値に丸められます——そしてこの丸めこそが目に見える「誤差」を引き起こします。
実務で本当に困る場面:ECサイトの合計金額
ECサイトのカートで各商品の価格を単純なfloatとして足し合わせていくと、商品数が増えるにつれて合計金額が期待値から1円ずれることがある——1回ごとの丸め誤差はごくわずかでも、打ち消し合わずに蓄積していくためだ。決済システムが円単位ではなく整数の最小単位(銭など)で計算したり、専用のdecimal型を使ったりするのはこのためである。
なぜこれが必要か
- 金融や科学計算における予期しない丸め誤差を理解し診断する。
- 特定の浮動小数点数の正確な2進表現を確認する。
- 分数を厳密な等価性で比較することが悪い実践である理由を同僚や学生に説明する。
ゼロに近い非正規化数
数値の大きさが通常の表現(暗黙の1から始まる仮数)には小さすぎる場合、IEEE 754は非正規化(subnormal)モードに切り替わり、この暗黙の1を省略します——これにより、絶対値がさらに小さい数値を表現できますが、ゼロへの急激な飛躍ではなく段階的な精度の低下を代償とします。ゼロへのこの「なめらかな」移行は突然のゼロ化よりも優れていますが、一部のプロセッサでは非正規化数の計算は通常の計算より明らかに遅くなります。