たった4ビットで分かる!0.1が保存できない理由

広告

「0.1 + 0.2 が 0.3 にならないことがある」

この現象を見て、

「コンピュータって計算を間違えているの?」

と思ったことはありませんか?

実は、計算が間違っているのではありません。

値を保存するときに、すでに丸められていることが原因です。

今回は、本物の double よりもずっと小さい、

整数部1ビット・小数部3ビット

しか保存できない世界で考えてみましょう。


この世界で保存できる数

小数部が3ビットしかないため、保存できる値は次の8種類だけです。

2進数10進数
0.0000.000
0.0010.125
0.0100.250
0.0110.375
0.1000.500
0.1010.625
0.1100.750
0.1110.875

この表を見ると、

0.100(10進数)

という値は存在しないことが分かります。

つまり、この型では 0.1をそのまま保存することはできません。


0.1を2進数に変換すると?

10進数の

0.1

を2進数へ変換すると、

0.000110011001100110011…

となります。

途中で終わらず、ずっと続く数です。


保存できるのは3ビットだけ

ところが、この型では

0.xxx

までしか保存できません。

そのため、

0.000110011…

を途中で丸める必要があります。

近い値を探すと、

保存候補10進数
0.0000.000
0.0010.125

となります。

10進数の0.1は、

  • 0.000との差:0.100
  • 0.125との差:0.025

となるため、

0.125の方が近いことが分かります。

その結果、

0.100
      ↓
0.001₂
      ↓
0.125

として保存されます。

つまり、

保存したつもりは

0.100

ですが、

実際に保存されているのは

0.125

なのです。


この状態で計算すると?

例えば、

0.1 + 0.1

を計算したいとします。

しかし、コンピュータの中では

0.125 + 0.125

になっています。

その結果は、

0.250

です。

本来期待していた

0.200

とは違う結果になります。

つまり、

計算が間違ったのではなく、保存された値が最初から違っていた

ということです。


実際のdoubleも同じ

ここまでの例は、

理解しやすいように

小数部3ビット

という極端な例で説明しました。

実際のC#の double は約53ビットの精度があります。

そのため誤差は非常に小さくなりますが、

仕組みは全く同じです。

0.1
    ↓
2進数へ変換
    ↓
無限に続く
    ↓
保存できるビット数まで丸める
    ↓
近似値として保存

つまり、

ビット数が違うだけで、本質は同じなのです。

だから実際には、

Console.WriteLine(0.1 + 0.2);

を実行すると、

0.30000000000000004

のような結果になることがあります。

これは計算ミスではなく、

保存された値が近似値だからです。


decimalではどうなる?

C#には decimal 型があります。

decimal は10進数を基準として値を保持するため、

decimal a = 0.1m;
decimal b = 0.2m;

Console.WriteLine(a + b);

とすると、

0.3

と表示されます。

そのため、

  • 金額
  • 消費税
  • ポイント計算
  • ファンクションポイント法

など、

10進数で正確さが求められる計算では decimal がよく使われます。


まとめ

今回のポイントは次の4つです。

  • コンピュータは小数を2進数で保存する。
  • 0.1は2進数では無限に続く数になる。
  • 保存できるビット数まで丸められるため、最初から近似値になる。
  • double も同じ仕組みで、違うのは使えるビット数が多いだけである。

「コンピュータは計算を間違える」のではなく、

保存できる形に丸めてから計算している。

これが、丸め誤差の正体なのです。

イラストの差し込み位置

  • アイキャッチ(記事の一番上)
    • 「4ビットしかない世界で、0.1が入りきらずに丸められる」イメージ
  • 「この世界で保存できる数」の直後
    • 8段の階段状に「0.000 → 0.001 → … → 0.111」と並び、0.1がその間にあることを示す図
  • 「保存できるのは3ビットだけ」の直後
    • 「0.1 → 2進数 → 無限に続く → 0.125へ丸められる」のフローチャート
  • 「この状態で計算すると?」の直後
    • 「0.1(保存時に0.125)+0.1(保存時に0.125)=0.25」の流れを矢印で表した図

この構成なら、生徒さんは「誤差は計算時ではなく保存時に発生する」という本質を直感的に理解しやすくなります。

訪問数 1 回, 今日の訪問数 1回

広告

C#

Posted by hidepon