メインコンテンツまでスキップ

小数計算の誤差

JavaScriptの小数の計算には誤差が生じる場合があるので要注意です。たとえば、0.1 + 0.2は0.3になってほしいところですが、計算結果は0.30000000000000004になります。これはJavaScriptのバグではありません。

javascript
0.1 + 0.2 === 0.3; //=> false
javascript
0.1 + 0.2 === 0.3; //=> false

number型はIEEE 754という規格に準拠していて、その制約によって生じる現象です。10進数の0.2は有限小数ですが、それを2進数で表すと0.0011...のような循環小数になります。循環小数は小数点以下が無限に続きますが、IEEE 754が扱う小数点以下は有限であるため、循環小数は桁の途中で切り捨てられます。その結果、小数の計算に誤差が生じてしまうわけです。これはちょうど、私達が円周率の計算を筆算するときの制約に似ています。円周率は3.141592...と無限に小数点以下が続きますが、時間も紙面も有限なため、ある程度の誤差は妥協して3.14に丸めて計算するかと思います。ちなみに、2進数で有限小数になる0.5や0.25などの数値だけを扱う計算は誤差なく計算できます。

javascript
0.5 + 0.25 === 0.75; //=> true
javascript
0.5 + 0.25 === 0.75; //=> true

小数計算の誤差を解決するために、一度整数に桁上げして計算し、もとの桁を下げる方法が考えられます。整数の計算は誤差が生じないという特性に期待した方法です。たとえば、110円の消費税込価格を求める計算を考えてみましょう。110に1.1を掛け算すると、誤差が生じて121円ぴったりにはなりません。

javascript
110 * 1.1; //=> 121.00000000000001
javascript
110 * 1.1; //=> 121.00000000000001

そこで、110と桁上げした税率11を掛け算してから、10で割ってみます。すると、うまく計算できます。

javascript
(110 * 11) / 10 === 121; //=> true
javascript
(110 * 11) / 10 === 121; //=> true

この方法を使う場合は、桁を戻した数値は小数になることがあり、その値には小数計算誤差問題が残り続けることに注意してください。

javascript
const price1 = (101 * 11) / 10; // 111.1
const price2 = (103 * 11) / 10; // 113.3
price1 + price2; // 224.39999999999998
javascript
const price1 = (101 * 11) / 10; // 111.1
const price2 = (103 * 11) / 10; // 113.3
price1 + price2; // 224.39999999999998

小数計算の誤差問題を包括的に解決したい場合は、decimal.jsのような計算誤差がないパッケージを使うのも手です。