MENU

JavaScriptとIEEE754倍精度浮動小数点数のすべて:数値誤差の正体と対策

目次

はじめに

JavaScriptの数値型「Number」は、整数も小数もすべて IEEE 754規格の倍精度浮動小数点数(64ビット) で表現されます。  

つまり、1 も 3.14 も 9007199254740991 も、内部的には同じ形式で処理されるのです。

この「すべてをひとつの形式で扱う」という仕様はシンプルで便利な反面、数値誤差や表現できる範囲の制限といった問題の原因にもなります。

イティセル/コード専門官

要するに、JavaScriptの「Number」は、整数型や小数型を分けずに ひとつの強力なフォーマット(倍精度浮動小数点数)に統一したものであり、それがそのまま「Number型」として定義されているのです。

IEEE 754倍精度浮動小数点数とは

IEEE 754は、コンピュータで実数を表現するための国際規格です。  

その中で「倍精度(double precision)」と呼ばれる形式は64ビットを使い、次のように構成されています。

•  符号部(1ビット):数が正か負かを表す

•  指数部(11ビット):数の大きさ(2の何乗か)を決める

•  仮数部(52ビット):数の細かい部分(有効桁)を保持する

この仕組みによって、非常に広い範囲の数値を扱うことができます。  

例えば、10の308乗といった巨大な数から、10のマイナス308乗のような極小の数まで表現可能です。

イティセル/コード専門官

倍精度を使えば、10兆や1000京といった桁外れの数値も扱えます。制限はありますが、日常的なプログラミングではほとんど気にしなくてよいほど遠い範囲です。

ただし

64ビットという有限の枠に収めるため、0.1や0.2のように2進数で割り切れない数は近似値として保存されることになります。  

その結果、JavaScriptでよく見られる 0.1 + 0.2 = 0.30000000000000004 のような誤差が生じるのです。

0.1 + 0.2  => 0.30000000000000004
イティセル/コード専門官

整数は2進数で正確に表せるため誤差が出にくいですが、
小数は「2進数で無限に続く小数」になってしまうため、どうしても誤差が避けられないのです。

表現できる範囲と限界

JavaScriptのNumber型が表現できる範囲は以下の通りです

イティセル/コード専門官

JavaScriptの Number 型には表現できる最大値があります。  
それが Number.MAX_VALUE ≈ 1.7976931348623157e+308 です。

この値を超えると、計算結果は Infinity(無限大) になります。  
つまり「オーバーフローした」と捉えてよいでしょう。

最大値 ≈ 1.8 × 10^308  

→ これ以上の数は「Infinity」になる。

console.log(Number.MAX_VALUE); 
console.log(Number.MAX_VALUE * 2); 
リクナ / JavaScript統括官

1.7976931348623157e+308
Infinity

実行結果
1.7976931348623157e+308
Infinity
イティセル/コード専門官

JavaScriptの Number 型で表現できる最小の正の値は Number.MIN_VALUE ≈ 5 × 10^-324 です。  
これは「0より大きいが、これ以上小さくできない極限の値」です。

これより小さい数は、0として扱われてしまいます(アンダーフロー)。

最小の正の値 ≈ 5 × 10^-324  

→ これより小さい正の数は「0」として扱われる。

console.log(Number.MIN_VALUE);
console.log(Number.MIN_VALUE / 2);
リクナ / JavaScript統括官

5e-324
0

実行結果
5e-324
0
イティセル/コード専門官

最大値を超えると、1を足しても2を足しても結果はすべて Infinity となり、無限大に吹き飛んでしまいます。  
逆に、最小の正の値を下回ると、1を引いても2を引いても結果はすべて 0 となり、ゼロに吸い込まれてしまいます。

さらに整数についても制約があり、安全に表現できるのは -(2^53 – 1) ~ (2^53 – 1) の範囲だけです。  

安全に整数を表現できる範囲 -(2^53 – 1) ~ (2^53 – 1)  

→ これを超えると「1刻みの差」を区別できなくなる。

console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2);
イティセル/コード専門官

これを実行すると、  
「9007199254740992 と 9007199254740992 は等しいか?」という判定になり、結果は…ぽちっ

リクナ / JavaScript統括官

true

実行結果
true
イティセル/コード専門官

つまり、範囲を超えると +1 も +2 も同じ値に丸められてしまうため、true になるのです。

`NaN`や`Infinity`もIEEE 754由来

イティセル/コード専門官

IEEE 754 では「数値演算の結果が通常の数値で表せない場合」に備えて、特別な値として Infinity や NaN が定義されています。

簡単にいうと、これらの「特殊値」は数の世界の 警備員 のような存在です。

Infinity:範囲を超えた計算結果

イティセル/コード専門官

Infinity の警備員:「これ以上は増やせません!」と範囲を超えた数を止める。

console.log(1 / 0);
リクナ / JavaScript統括官

Infinity

実行結果
Infinity

NaN (Not a Number):数値演算が不正な場合

イティセル/コード専門官

NaN の警備員:「この計算は不正です!」と割り切れない演算を弾く。

console.log(0 / 0);
リクナ / JavaScript統括官

NaN

実行結果
NaN

誤差を回避する方法

1. 丸め処理を行う

イティセル/コード専門官

Numberは少数を正確に扱うのが苦手です。  
例えば 0.1 + 0.2 を実行すると、本来の 0.3 ではなく 0.30000000000000004 という微妙な誤差が出てしまいます。

こうした誤差を避けるには、丸め処理を行う関数を用意して「小数点以下を何桁まで扱うか」を指定してあげるのが一般的です。

function round(num, digits) {
  return Math.round(num * 10 ** digits) / 10 ** digits;
}
console.log(round(0.1 + 0.2, 2));
リクナ / JavaScript統括官

0.3

実行結果
0.3

2. 整数に変換して計算する

イティセル/コード専門官

小数のまま計算すると誤差が出やすいため、いったん整数に変換してから計算し、最後に小数に戻すという方法もあります。とてもシンプルなやり方です。

console.log((0.1 * 10 + 0.2 * 10) / 10);
リクナ / JavaScript統括官

0.3

実行結果
0.3

3. BigIntを使う(整数のみ)

イティセル/コード専門官

どうしても 9007199254740991(Number.MAX_SAFE_INTEGER) を超える整数を正確に扱いたい場合は、BigInt を使う方法があります。

BigInt を使うときは、数値リテラルの末尾に n を付けるのがルールです。

const big = 9007199254740991n + 2n;
console.log(big);
リクナ / JavaScript統括官

9007199254740993n

実行結果
9007199254740993n → 9007199254740991を超えてる

まとめ

JavaScriptの Number 型は、整数も小数もすべてを IEEE 754 倍精度浮動小数点数 というひとつの器に押し込めた設計思想の産物です。  

そのシンプルさは「1 も π も天文学的な数も同じ形式で扱える」という強みをもたらす一方で、誤差・範囲・精度という宿命的な制約を背負わせました。

•  誤差:0.1 + 0.2 が 0.3 にならないのは、2進数で有限表現できない小数を近似値で保存しているから。

•  範囲:最大値を超えれば Infinity に吹き飛び、最小値を下回れば 0 に吸い込まれる。

•  精度:整数も安全に扱えるのは ±(2^53 – 1) までで、それを超えると「+1」と「+2」の違いすら消えてしまう。

これらの“警備員”の存在を理解したうえで、丸め処理・整数変換・BigInt・専用ライブラリといった対策を適材適所で使い分けることが、堅牢な数値処理への第一歩です。

要するに、Number型は万能ではないが、その限界を知り、補う術を持てば十分に戦える武器になる。  

これが、JavaScriptの数値と正しく付き合うための核心です。

もしこの記事が役に立ったと思ったら、シェアやコメントで教えてください。  

いただいた声を今後の改善に活かしていきます。  

最後まで読んでくださり、本当にありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ITTIのアバター ITTI 運営長

私はフロントエンドエンジニアを目指す初心者で、ITパスポートを取得済みです。現在はCopilotを活用しながらAIや最新のIT技術を学び、日本の開発現場で求められるチーム開発やセキュリティの知識を吸収しています。学んだことはコードや仕組みを整理し、わかりやすく発信することで、同じ学びの途中にいる人たちの力になりたいと考えています。

コメント

コメントする

目次