JavaScriptで小数を扱う際の注意と桁の多い数字を扱う場合はBigInt型を推奨
この記事の要約 JavaScriptで小数や桁数の大きい数値を計算で扱う場合は誤差が出る場合があるので、小数の計算方法の注意や桁数の多い数値はBigInt型を使うなど対応しなければなりません。
JavaScriptで0.1と0.2を足したら綺麗に0.3にならないのは何故?
const x = 0.1 + 0.2;
if(x === 0.3) {
alert("xは0.3だよ");
} else {
alert("xは0.3ではないよ");
}
上記を実行すると「xは0.3ではないよ」というメッセージが表示されます。その数値を調べるためにconsole.logなどでxの値を表示すると「0.30000000000000004」と出ました。
JavaScriptのNumber型は、IEEE 754規格の64ビット倍精度浮動小数点数で、64ビットの内訳は
符号:1ビット (プラスかマイナスか)
指数部:11ビット (数値の桁数について)
仮数部:52ビット (実質53ビットの2進数で数値を表す)
この仮数部に数値が格納されるのですが、10進数の「0.1」を2進数に変換すると0.0001100110011...と無限に循環する小数になります。
メモリには限りがあるため、これを有限ビット数に収まるよう、丸め処理を行って保存します。その結果、保存された数値を厳密な10進数に戻すと、実際には「0.1」ではなく「0.10000000000000000555...」という微妙にズレた値になっています。
17桁目まで0が続き、微細な値は切り捨てられ綺麗に数桁の小数が表示されるのか、はたまた小数17桁の中に丸め誤差で切り上げられ、数値が長く並ぶのか決まります。
下記も無限小数が丸められて、誤差が出た表示になります。
let x;
x = 0.1 + 0.2;
console.log(x);
x = 0.1 + 0.7;
console.log(x);
x = 0.1 + 1.1;
console.log(x);
x = 0.1 + 1.3;
console.log(x);
小数の誤差(0.1 + 0.2)はどう解決する?
BigIntは小数を使えません。そのため、小数の計算誤差をなくしたい場合は、一度整数にしてから計算するのが定石です。
// 10倍して整数にしてから計算し、最後に10で割る
const x = (0.1 * 10 + 0.2 * 10) / 10;
console.log(x); // 0.3 と表示される
このページの内容をベースに作った動画から、ライブラリを使うのも良いですよ!とコメントをいただきました。ありがとうございます!
調べるとdecimal.jsというライブラリなどが有名みたいです。
JavaScriptでNumber型の安全な範囲
0.1などはNumber型です。そのため下記の範囲が安全な範囲です。
範囲:−2^53+1 〜 2^53-1
実際:-9,007,199,254,740,991 〜 9,007,199,254,740,991
範囲を超えると小数ではなくても計算結果に誤差が出るので注意!
const x = 9007199254740991 + 1;
// 9007199254740992で正常
console.log(x);
const y = 9007199254740991 + 2;
// 9007199254740992でプラス2したのに1しか増えていない!(誤差)
console.log(y);
これは、数値が巨大すぎて「1」という細かさを表現できなくなり、メモリ上の目盛りが「2飛び」になってしまったためです。2進数の桁が足りず、細かい数字が無視されてしまうのです。
他にも、加算しても値が変化しなかったり、意図しない値に丸められたりすることがあります。
JavaScriptでBigIntの使い方
下記のページでリテラルやプリミティブ型などを解説しました。その中にBigIntについても少しだけ載せています。
前のページ: 変数を深く知るためのリテラルについて徹底解説
なぜBigIntが必要なのか。まずは下のコメントアウトした結果の方だけ見てください。for文の中身は1ずつ加算して結果を表示しています。for文については別のページで解説します。
let x = 9007199254740991;
for (i = 0; i < 10; i++) {
console.log("x + " + i + " = " + (x + i) );
}
/*
結果
x + 0 = 9007199254740991
x + 1 = 9007199254740992
x + 2 = 9007199254740992
x + 3 = 9007199254740994
x + 4 = 9007199254740996
x + 5 = 9007199254740996
x + 6 = 9007199254740996
x + 7 = 9007199254740998
x + 8 = 9007199254741000
x + 9 = 9007199254741000
*/
BigIntを使って計算する方法
通常の数値はnumberになりますが、bigintにするには数値の末尾に「n」を付けるのでした。計算結果も「n」がついたままのBigInt型として扱われます。BigIntは値として常に「n」を伴いますが、文字列に変換すれば表示できます。文字列に変換するには変数に.toString()を付けるとできます。
const x = 9007199254740991n + 1n;
const y = 9007199254740991n + 2n;
// 9007199254740992で正常
console.log(x.toString());
// 9007199254740993で正常
console.log(y.toString());
大切なことは計算する際は型を合わせること。だから計算する値のどちらにも「n」を付けてBigInt型にしています。
文字列をBigInt型に変換して計算に使う方法
文字列(String型)をBigInt型にして計算できるようにする方法はBigInt()関数を使います。
// Number型の安全な範囲を超える文字列
const s = "9007199254740991";
// 文字列をBigInt型に変換して数値にして計算に使えるようにする
let x = BigInt(s);
y = x + 2n;
// 期待通りの9007199254740993を表示
console.log(y.toString());
ただし、DOM出力やテンプレート文字列では、暗黙的に文字列化されるため「n」が表示されない場合があります。
BigInt型と通常のNumber型を混ぜて計算しようとするとエラーになるため、計算時は必ず両方に「n」をつけて合わせる必要があります。
なぜNumber型は安全な範囲以外だとダメなのか?
Number型には約16桁の安全な範囲があります。表現するためのビットが足りなくなり、数値が丸められてしまうので誤差が出て、隣の整数と区別ができなくなり精度が落ちます。
BigIntが必要な場面
大きな数字といえばIDやお金などをイメージしますが、プログラムでは20桁くらいのID、エンコードやハッシュ、URL、バイナリ、暗号化などさまざまな巨大な数字を扱う場面があります。
JavaScript側で大きな数値を計算する場合、誤差が出ることもありますので、もし巨大な数値を扱うならBigIntにすると安全です。
この記事からみたよくある質問
- JavaScriptで0.1と0.2を足すと0.3にならないのは何故ですか?
- 2進数変換時に発生した微細な誤差が、足し算によって積み重なってしまうためです。単体の「0.1」などは表示時には仕様に基づいて最短の10進表現に変換されているため、計算で誤差が大きくなると、隠しきれずに「0.3000...4」のような端数が表に出てきてしまいます。
- JavaScriptで9007199254740991の最大値を超えて計算をする場合は、どうしたら安全ですか?
- 数値の末尾に「n」を付けてBigInt型にすると安全に巨大な数値を使った計算ができます。そのままだと一見、良さそうに見えても誤差が出る場合があるので注意が必要です。