なぜ 0.1 + 0.2 は 0.30000000000000004 になるのか

2進数では 0.1 を正確に表せないためです。JavaScript 固有の問題ではありません。どこまで正確なのか、安全な比べ方、お金の扱い方までを実行して確かめます。

実行して検証済み処理系Node 22.22.3対応ES1難易度●●●○○目安10更新

2進数では 0.1 を正確に表せないからです。 1/310進数で書くと 0.333… と終わらないのと同じことが、0.1 に起きています。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
0.30000000000000004 false
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
0.10000000000000000555 0.20000000000000001110 0.29999999999999998890

これは JavaScript の不具合ではありません。IEEE 754倍精度を使う言語では Python でも Java でも C でも同じ結果になります。

実際に何が入っているのか

0.1 の中身をもっと桁数を出して見ます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
true 0.50000000000000000000 0.10000000000000000555
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
9007199254740991 true 9007199254740992 false

0.1少しだけ大きく0.3少しだけ小さく保存されています。 0.1 + 0.2 はわずかに大きい側に寄るので、0.3 と一致しません。

画面に 0.1 と出るのは、そう見えるように丸めているからです。 中身は最初から 0.1 ではありません。

正確に表せる数もある

2進数で書ける小数、つまり2の冪の和で作れる数は正確です。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
9007199254740993n bigint
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
false true

0.51/20.251/4 なのでぴったり入ります。 0.11/10 で、2進数では割り切れません。

整数はどこまで正確か

小数と違い、整数はある大きさまで正確です。

javascript
const a = 1e10 + 0.1;
const b = 1e10 + 0.2;

console.log(Math.abs(a - b) < Number.EPSILON);
console.log(a - b);
false -0.10000038146972656
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
true true false

9007199254740991(約9千兆)を超えると、隣り合う整数を区別できなくなります+1+2 が等しくなっているのがその証拠です。

3行目に注目してください。書いた数と、出てきた数が違います。 …993 は表せないので、リテラルを書いた時点でいちばん近い …992 に置き換わっています。

大きな整数を正確に扱うなら BigInt です。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
true true
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
199.9 true 7.000000000000001 false

どう比べればよいか

誤差を許して比べる

差が十分小さければ同じとみなします。Number.EPSILON1 の隣の数との差で、この種の判定によく使われます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
19990 199.90
javascript
console.log((1.005).toFixed(2), (1.005).toPrecision(20));
console.log((1.015).toFixed(2), (1.015).toPrecision(20));
console.log((1.025).toFixed(2), (1.025).toPrecision(20));
console.log((2.675).toFixed(2), (2.675).toPrecision(20));
1.00 1.0049999999999998934 1.01 1.0149999999999999023 1.02 1.0249999999999999112 2.67 2.6749999999999998224

ただしこれは小さい数でしか通用しません。 数が大きくなると、誤差そのものが大きくなります。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
1.01 1.02 1.03
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
0.1 true

大きさに合わせて許容量を変える必要があります。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
0.2 0.30000000000000004 0.30000000000000004441

整数にしてから比べる

そもそも小数にしない方法もあります。これがいちばん確実です。

お金は小数で持たない

金額を 19.99 のような小数で持つと、足すたびに誤差が出る……とは限りません。 ここが厄介なところです。

19.9910回足すとぴったり合い、0.710回足すとずれます

どちらになるかは値によって変わり、見ただけでは分かりません。 「試したら合っていた」は何の保証にもなりません。手元で通ったものが、別の値でずれます。

だから最小単位の整数で持ってください。円なら円、ドルならセントです。

toFixed は思ったとおりに丸まらない

「見せるときだけ丸めればいい」も、そのままでは通りません。

4つとも切り下がっています。 どれも中身が見た目よりわずかに小さいからです。 1.005 の実体は 1.00499999999999989… なので、 四捨五入して 1.00 になるのが正しい動きです。

toFixed() が間違っているのではありません。渡している数が、もう 1.005 ではないのです。

「見た目の値」で四捨五入したいなら、誤差を足し戻してから丸めます。

これも万能ではありません。金額を扱うなら、最初から整数で持つのが確実です。

よくある誤解

JavaScript だけの問題だと思っている

IEEE 754倍精度を使う言語すべてで起きます。 0.1 を正確に持てる言語は、10進数の型を別に用意しています (Java の BigDecimal、Python の decimal など)。

表示された値が中身だと思っている

console.log(0.1)0.1 と出るのは、 その値に戻る最短の10進表記を選んで表示しているからです。

同じ値です。 見た目が違うだけです。

誤差は「たまに」出ると思っている

0.1 を足すたびに毎回ずれています。表示のときに隠れているだけです。

2回足したときは表示上ちょうど 0.2 ですが、 3回足すと隠しきれなくなって出てきます。

同じ IEEE 754 が決めている NaN の振る舞いは なぜ NaN === NaN は false なのか にあります。

「等しい」をどう判定するかという話は、===== の違いにも繋がります。 → なぜ == ではなく === を使うのか

この記事の根拠

  1. The Number Type — ECMAScript® 2026 Language Specificationtc39.es
  2. Number.prototype.toFixed — ECMAScript® 2026 Language Specificationtc39.es
  3. IEEE 754-2019 — IEEE Standard for Floating-Point Arithmeticstandards.ieee.org

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)