JavaScriptで小数点の計算に誤差が発生する原因とその解決策
はじめに
こんにちは、エンジニアのとうめです。
こちらはアドベントカレンダー26日目の記事です。2024年ももう少しで終わってしまいます。
今年やるべきことを来年に持ち越してしまわないよう、残り数日頑張りましょう!
なぜこの記事を書いたか
とある案件で、金額や金利を入力してもらえる利息を計算するというシミュレーターの開発を依頼されました。
入力された値に対して、特定の計算式で計算して結果を出すというとてもシンプルなものなので、すぐに開発を終わらせ、いざ検証すると不思議なことが起きました。
「計算が合わないパターンがある…」
最初は実装した計算式に問題があるのではないかと見直したのですが特に問題なし…。
提供されたテストシートのほうも確認したのですが、計算式や入力値にも問題ありませんでした。
例えば
以下をJavaScriptで計算するとどうなるでしょうか?
0.1 x 0.1
「0.01」と出ると思ったそこのあなた、惜しいです。
正解は「0.010000000000000002」でした。
もちろん、すべての計算に誤差が発生することはありません。
例えば以下の計算式のように正しく表示される場合もあります。
0.1 x 0.3
このように、計算式によって誤差が発生したり正しい結果が出たりします。
小数点3桁以降は切り捨てなどの対応をしていれば問題ないかもしれませんが、計算式や計算する数値によっては不具合になります。
一体何が原因なのか…?
JavaScriptの数値型の問題:浮動小数点の罠!
JavaScriptの数値はIEEE754形式(浮動小数点)で出来ています。
細かい説明は省きますが、浮動小数点数の計算は2進数を利用するため、2進数で表現できない数値は近似値で処理されます。この仕組みが原因で計算結果に誤差が生じます。
計算の誤差をなくすには?
JavaScriptの計算の誤差を解決する一番簡単な方法は、専用のライブラリを利用することです。
「Numeral.js」や「BigInt」など複数ありますが、今回は機能がシンプルで軽量の「decimal.js」を使用します。
方法1 ライブラリを利用する
- decimal.jsをダウンロードする。
- HTMLにJSファイルとして読み込む。
※パスは適宜修正してください。
<script src="decimal.js"></script>
- 計算式に組み込む。
例えば以下のプログラムの場合
let a = 0.1;
let b = 0.2;
console.log(a * b); // 0.020000000000000004 になる
以下のように修正する。
let a = new Decimal("0.1");
let b = new Decimal("0.2");
console.log(a * b); // 0.02になる
まとめ
今回はjavascriptの例を挙げましたが、同様の事象はPHPなどほかの言語でも起こりえます。
検証に利用する数値によっては不具合が発覚しづらいものなので、小数点の計算では誤差が起きることを覚えておいても損しないと思います。
参考
浮動小数点演算誤差:http://www.juraris.co.jp/act/error_dmg.html
利用したライブラリのドキュメント:https://mikemcl.github.io/decimal.js/
この記事を書いた人
Advent Calendar!
Advent Calendar 2024開催中!