小数計算の誤差 0.1 + 0.2 が 0.30000000000000004 になる理由

プログラム
プログラム
スポンサーリンク

JavaScript で「0.1 + 0.2」のような小数の計算をすると、答えが「0.30000000000000004」になり誤差が発生することがあります。JavaScript でプログラムを組んだことがある方なら、一度は経験したことがあるのではないでしょうか? そこで今回は、なぜ 0.1 + 0.2 が 0.30000000000000004 になるのか、この誤差の原因と回避する方法をまとめてみました。

この誤差の原因

この誤差の根底にある原因を一言でいうと

ほとんどの10進数の「小数」は、2進数に正しく変換できないから

です。(10進数の「整数」はすべて2進数に正しく変換できます)

JavaScript に限らずコンピューターで計算をする場合は、10進数から2進数に変換してから計算して、計算結果をまた2進数から10進数に戻して表示してくれています。

例えば 2 + 3 は、コンピューターでは次のように計算されています。(10進数、2進数の変換方法はこのあと説明しますので、なんとなくそういうものだとご理解ください)

コンピューターで 2 + 3 を計算する過程

コンピューターも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 になるまで余りを求めます。

10進数「13」を2進数に変換

求めた余りを下から並べた「1101」が、2進数で表した「13」になります。

●小数の変換
続いて小数の10進数「.875」を2進数に変換します。「小数」の10進数を2進数に変換するには、10進数の小数部分だけを「2」倍にしつづけて、小数部分が 0 になるまで整数部分を取り出します。(文章で書くとわかりづらいので下の計算例でご確認ください)

10進数「.875」を2進数に変換

取り出した整数部分を上から並べた「111」が、2進数で表した「.875」になります。そして整数部分の「1101」と合体させた「1101.111」が、10進数の「13.875」を2進数に変換したものです。

循環小数

さてこの要領で本題の 0.1 + 0.2 の10進数の小数「0.1」と「0.2」を2進数に変換してみましょう。

「0.1」= 0.0001100110011001100110011001100110011001100110011001101・・・
10進数「0.1」を2進数に変換

「0.2」= 0.001100110011001100110011001100110011001100110011001101・・・
10進数「0.2」を2進数に変換

なんと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」などだと誤差が少ないためかうまくいくので、気がつかずに痛い目にあいました(^^;) やはり基本的な仕組みを理解しておくことは大事ですね。

コメント

タイトルとURLをコピーしました