導入
JavaScriptにおいて、数値計算を行う際に「0.1 + 0.2 が 0.30000000000000004 になる」という現象に頭を抱えた経験はないでしょうか?これはJavaScriptが数値をIEEE 754という規格の浮動小数点数(Floating-point literal)として扱っているために発生する仕様です。実務において、金銭計算や厳密な判定が必要な場面でこの性質を理解していないと、重大なバグに繋がります。今回は、浮動小数点リテラルの特性と、現場で安全に計算を行うためのベストプラクティスを解説します。
基礎知識
JavaScriptの数値型(Number)は、64ビットの倍精度浮動小数点形式です。これは「仮数部」「指数部」「符号部」の3つで構成されており、人間が日常的に使う10進数を、コンピュータ内部で2進数に変換して保持します。問題は、10進数の多くの小数は2進数では「無限小数」となり、メモリの容量制限で打ち切られる(丸められる)点にあります。この「丸め誤差」の積み重ねが、意図しない数値の不一致を生む原因となります。
実装/解決策
実務における解決策は大きく分けて2つあります。
1. 計算前に整数に変換する: 金銭計算などで最小単位(円やセント)を整数として扱う手法です。
2. ライブラリを利用する: 厳密な精度が必要な場合は「decimal.js」や「big.js」などのライブラリを導入するのが業界標準です。
また、単なる比較であれば、許容誤差(イプシロン)を設けて判定する方法も有効です。
サンプルプログラム
以下のコードは、JavaScriptでよく遭遇する誤差の問題と、それを回避する現実的なアプローチを示しています。
// 1. 誤差が発生する例
console.log(0.1 + 0.2 === 0.3); // false (0.30000000000000004 になるため)
// 2. 許容誤差(Number.EPSILON)を使った比較方法
// 2つの数値の差が十分に小さければ等しいとみなす
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true
// 3. 金銭計算での推奨手法(整数に変換して計算)
// 100円を10000銭として扱うことで浮動小数点の誤差を回避する
const price = 0.1;
const tax = 0.08;
const total = (Math.round(price 100) + Math.round(tax 100)) / 100;
console.log(total); // 0.18 (期待通りの結果)
応用・注意点
現場での注意点として、toFixed()メソッドの挙動に注意してください。toFixed()は文字列を返すため、そのまま比較演算を行うと予期せぬ型変換が発生します。また、極端に大きな数値や小さな数値を扱う場合は、Number型ではなくBigInt型の使用を検討してください。ただし、BigIntは整数のみを扱うため、小数が絡む計算では引き続き外部ライブラリの利用が推奨されます。プロジェクトの要件に応じて、パフォーマンスと精度のどちらを優先すべきか判断することが、シニアエンジニアとしての腕の見せ所です。