コンピューターでは 0.1+0.2 が 0.3 ではない理由
🌼 はじめに
コンピューターでは
0.1 + 0.2 === 0.3 // false
JavaScript だけではなく、ほとんどのプログラミング言語は同じような結果を出力します。
今回はその理由を解説していきたいと思います。
メモリが数字を保存する方法
コンピューターがプログラムを実行するとき諸々データはメモリに保存されるので、数字もメモリに保存されます。
ですが、コンピューターの世界観では
小数はどう保存されるのか
では
ほとんどの場合は小数を保存するとき IEEE[1] という団体が決めた標準を採用しています(IEEE 754)。この方式では小数を
実際やってみましょう。
- 小数点より左が
になるように小数点を左もしくは右に移動1 - 小数点を移動した数字に
を掛ける2^{e} - 左に
マス移動した場合はn e = +n - 右に
マス移動した場合はn e = -n
- 左に

正規化の例
これで正規化できたので、実際どう保存されるかを見ていきます。32ビットに保存する場合を基準として話すので、まずは32ビットのメモリ空間を確保します。

1番最初の1ビットは符号部です。ここには保存する数字が正の数だったら

符号部
その次の8ビットは指数部です。ここには
- 指数
にe を足す127 - それを2進数に変換する
🙋♀️なんで127を足すんですか?
指数は正の数になることも(ex.
その

指数部
残り23ビットは仮数部です。

仮数部
このように実数をフォーマットする方法を浮動小数点(floating point)と言います。小数点が固定ではなくて浮いてる(移動してる)から浮動と言ってるんですかね知らんけど
0.1+0.2≠0.3 になる理由
でも浮動小数点方式では正確な値を保存できない場合があります。
例えば10進数

23ビット(仮数部)に収まらない
保存容量には限界があるし無限に溢れてる数字を全部保存するわけにはいかないので、23ビットの最後のビットで1に切り上げるか、0に切り捨てることになります。[2]

この切り上げ・切り捨てが原因で誤差が発生します。
なのでコンピューターで
ふと気になって JavaScript で
0.1 + 0.2 // 0.30000000000000004
小数を扱う時は要注意
正確な数値を扱わないといけないプロジェクトの場合、浮動小数点の誤差をちゃんと考慮しないとその誤差が原因で事故が起きることもあります。[3]
でも浮動小数点を理解していたら、正確な数字を扱わないといけない場面が来ても色々工夫できるでしょう。
例えばお金の場合、整数に変換することで正確な値が保存できます。
また、数字の比較のときは 0.1 + 0.2 === 0.3 のように直接比較するのではなく、誤差を比較する方法があります。
例えば
const nearlyEqual = (a: number, b: number, eps: number = 1e-9): boolean => {
return Math.abs(a - b) <= eps;
}
nearlyEqual(0.1 + 0.2, 0.3) // true
1e-9は
他にはfloat(32ビット)じゃなくてdouble(64ビット)で数字を保存する方法があります。double(64ビット)だと仮数部の容量がfloat(32ビット)に比べて2倍以上増えるので、その分正確さが上がります。

double(64ビット)で保存する場合
仮数部52ビットの最後で切り上げ・切り捨てをするのでまだ正確な値ではありませんが、float(32ビット)よりもっと正確度の高い値を保存することはできます。その分メモリ容量も2倍使いますが、最近はハードウェアのスペックも良くなったのでそこまで気になる範囲ではないかもしれません。
ちなみに TypeScript ではfloatもdoubleもなく大体numberが使われていますが、numberも64ビットに保存されます。
🌷 終わり
ちょっとしたCS勉強メモでした。
地味に日本語の数学用語が難しい、正の数・負の数とか四捨五入とか初めて知った
-
IEEE(Institute of Electrical and Electronics Engineers): 電気・電子・情報分野の国際的な学会/標準化団体 ↩︎
-
いつ切り上げるか、もしくは切り捨てるかの基準はIEEE 754標準で決まってる ↩︎
-
湾岸戦争時
の誤差が累積されミサイルの防御が失敗し、28人が死亡。パトリオットミサイル wikipedia の「ダーランでの失敗」項目参照 ↩︎0.1
Discussion