JavaScript で「0.1 + 0.2」のような小数の計算をすると、答えが「0.30000000000000004」になり誤差が発生することがあります。JavaScript でプログラムを組んだことがある方なら、一度は経験したことがあるのではないでしょうか? そこで今回は、なぜ 0.1 + 0.2 が 0.30000000000000004 になるのか、この誤差の原因と回避する方法をまとめてみました。
この誤差の原因
この誤差の根底にある原因を一言でいうと
です。(10進数の「整数」はすべて2進数に正しく変換できます)
JavaScript に限らずコンピューターで計算をする場合は、10進数から2進数に変換してから計算して、計算結果をまた2進数から10進数に戻して表示してくれています。
例えば 2 + 3 は、コンピューターでは次のように計算されています。(10進数、2進数の変換方法はこのあと説明しますので、なんとなくそういうものだとご理解ください)
コンピューターも10進数で計算すれば良さそうですが、コンピューターは「ON(1)」と「OFF(0)」を表すスイッチのかたまりのような物で作られているため、「1」と「0」のみの2進数で計算したほうが都合がいいのです。
10進数の小数が2進数に正しく変換できない理由
なぜ10進数の「小数」が2進数に正しく変換できないのでしょうか? それではまず10進数から2進数への変換方法を確認してみましょう。
10進数から2進数への変換方法
ここでは例として「13.875」を、10進数から2進数に変換してみます。整数部分の「13」と、小数部分の「.875」は変換方法が違いますので、はじめに整数の「13」を2進数に変換します。
●整数の変換
「整数」の10進数を2進数に変換するには、変換したい10進数を「2」で割りつづけて、答えが 0 になるまで余りを求めます。
求めた余りを下から並べた「1101」が、2進数で表した「13」になります。
●小数の変換
続いて小数の10進数「.875」を2進数に変換します。「小数」の10進数を2進数に変換するには、10進数の小数部分だけを「2」倍にしつづけて、小数部分が 0 になるまで整数部分を取り出します。(文章で書くとわかりづらいので下の計算例でご確認ください)
取り出した整数部分を上から並べた「111」が、2進数で表した「.875」になります。そして整数部分の「1101」と合体させた「1101.111」が、10進数の「13.875」を2進数に変換したものです。
循環小数
さてこの要領で本題の 0.1 + 0.2 の10進数の小数「0.1」と「0.2」を2進数に変換してみましょう。
「0.1」= 0.0001100110011001100110011001100110011001100110011001101・・・
「0.2」= 0.001100110011001100110011001100110011001100110011001101・・・
なんと10進数の小数「0.1」と「0.2」を2進数に変換すると、どちらも「循環小数」(ある桁から先で同じ数字の列が無限に繰り返される小数)になってしまうのです!
先ほどの10進数の「13.875」は、正しく2進数に変換できましたが、これはめずらしいことで例えば一桁の小数 0.1 〜 0.9 のうち正しく2進数に変換できるのは 0.5(2進数にすると 0.5 × 2 = 1.0 すなわち 0.1 になります)のみです。そのためほとんどの小数は上のように「循環小数」になります。これが「ほとんどの10進数の小数は2進数に正しく変換できない」理由です。
「循環小数」は無限に続くので、コンピューターでもそのままでは計算できません。JavaScript では50桁あたりで四捨五入するため 「0.1 + 0.2 = 0.30000000000000004」のような誤差が発生してしまうのです。(詳しくは「IEEE754」や「浮動小数点数」で検索してください)
JavaScript でこの誤差を回避する方法
JavaScript でこの誤差を回避するには、必要な小数点以下桁数に四捨五入するしかありません。
JavaScript ではちょっと面倒なのですが、小数点以下が 1桁の場合は 10(2桁の場合は100)をかけてから四捨五入し、四捨五入した値を今度は 10で割ります。
var a = 0.1 + 0.2; // 0.30000000000000004 a = Math.round(a * 10); // 10をかけて→「3.0000000000000004」を四捨五入→「3」 a = a / 10; // 「3」を10で割る console.log(a); // 0.3
おわりに
以前私は『ハイハイ「丸め誤差」ってやつね〜(←ちゃんと分かってない)整数にしてから計算すればいいんだよ』とドヤ顔で、下のようなプログラムを書いていました。
var x = 0.14 * 100; // 14 のはず var y = 0.07 * 100; // 7 のはず var a = x + y; // 21 のはず console.log(a / 100); // 0.21のはず
が、実際にはこうなります。
var x = 0.14 * 100; // 14.000000000000002 var y = 0.07 * 100; // 7.000000000000001 var a = x + y; // 21.000000000000004 console.log(a / 100); // 0.21000000000000005
ほかの小数でもこんな結果になれば、プログラムテストの時に気がつくのですが、これが「0.15 + 0.08」などだと誤差が少ないためかうまくいくので、気がつかずに痛い目にあいました(^^;) やはり基本的な仕組みを理解しておくことは大事ですね。
コメント