コンピュータの計算は正確か?
■コンピュータは計算が得意!
あなたが書いたプログラムに計算を任せるとき、返ってくる答えは いつでも正確だと思っていませんか?
yes? no?
答えは、半分正解、半分誤りです。
どういう事でしょうか?
実際にプログラムに計算を行わせて見ていきましょう。
1.コンピュータは計算が得意!
まず、下記の計算式で考えて見ましょう。
12345 × 67890 = ?
もちろん、このような単純な計算は、コンピュータは大得意です。
私達にとって桁の大きな計算は暗算では難しく、筆算を使ってもこのような計算を間違える可能性はあります。
しかし、コンピュータは間違いません。
何回尋ねても、同じ答えが一瞬で返ってきます。
やはり、当たり前のようですが、コンピュータは計算が得意なようです。
2. コンピュータが間違う計算?
では、次の例はどうでしょうか?
今回は式ではありませんが、次の問題を考えてみましょう。
「0.1を100回足せ。」
簡単ですね。答えは「10」です。
コンピュータに計算を任せる前に、私達であれば一瞬で答えを導き出せます。
私達には簡単に思えるこの計算が、実はコンピュータは間違えます。
「こんな簡単な計算、コンピュータが間違うわけがないじゃないか。」
まあそう言わずに、実際に見てみましょう。
C言語でこの問題を書いてみましょう。
#include <stdio.h> int main(void) { int sum = 0; for(int i = 0; i < 100; i++) sum += 0.1; printf("答えは、%fです。", sum); return 0; }
こちらのプログラムは、0.1を100回足して、その結果を画面に表示するものです。
さて、こちらを実行すると、どうなるでしょうか?
> 答えは、10.000002です。
あれ?
正確に10.000000ではないですね。
何故でしょうか?
結論から言うと、誤差の原因はコンピュータが少数点数を扱う仕組みにあります。
■コンピュータの内部は2進数
ご存知かもしれませんが、コンピュータ内部ではデータの表現方法として2進数が使われています。
我々が画面を通して見ているのが10進数であっても、内部では2進数から変換してくれているのです。
これは10進数の整数であっても、少数点数であっても同じ事です。
(話が逸れるので詳しくは説明しませんが、今あなたが読んでいるこの文章も、
コンピュータは内部で2進数として解釈しています。)
10進数から2進数への変換を簡単に見てみましょう。
まず、整数です。
例えば、10進数の123は、2進数で表現すると、1111011となります。
では少数点数はどうでしょうか?
先ほどの、10進数の0.1を2進数で表現すると、次のようになります。
0.00011001100...(以下、1100の繰り返し)
見ての通り、循環小数となります。
10進数の0.1は、2進数ではちょうどよく表せないのです。
コンピュータ内のメモリも有限なので、循環小数はどこかで切って、近似値で表すしかなくなります。
これが、誤差の出る原因です。
■誤差が出るのは当然?
考えてみれば、近似値によって誤差が出るのは当然かもしれません。
私達が普段扱っている10進数でも似たようなことがありますよね?
「10を3で割って、その結果に3を掛ける」
を順番に行うと、
10 ÷ 3 = 3.33333.....(循環小数)
3.333333 × 3 = 9.999999
こうなりますよね?
このような事が、2進数でも起こってしまうわけです。
■誤差を避けるにはどうする?
誤差の出る原因はわかりましたが、これらの回避策はあるのでしょうか?
まず1つ目の回避策は、ずばり「誤差を無視する」事です。
ふざけていませんよ。
今回の誤差は非常に小さなもの(0.000002)なので、無視してもいいケースもありそうです。
そして2つ目の回避策は、「少数点数を整数として扱う」事です。
今回のプログラムは「0.1を100回足す」と言うものでしたが、これを以下のように変更します。
「1を100回足し、その後sumの値を10で割る。」
こうすれば、少数点数を使わなくてよく、誤差が出ることもありません。
ちなみに、小さな誤差も許容できない銀行のプログラムなどでは、
BCD(Binary Coded Decimal)と言う形式を用いて計算誤差を無くしているみたいです。
BCDでは、10進数の数字1桁ごとに2進数を4桁(4ビット)使います。
■プログラムを書く時には誤差に注意!
今回取り上げた誤差は一つでしたが、その他にも誤差の種類は様々です。
誤差のせいでプログラムが暴走しないように、コンピュータを信じすぎないようにしましょう。
■コラム
今回はC言語プログラムで誤差を見せましたが、同じ計算を手元のrubyプログラムで行ったところ、誤差は出ませんでした。
これはコンパイラがプログラムを最適化してくれているおかげです。
rubyプログラムが内部で
「0.1を100回足しているから、これは0.1 × 100をすればいいね。」
と解釈してくれるおかげで、誤差が出なくなります。
回避策の2つ目のような事を自動で行ってくれているわけですね。
C言語のコンパイラgccでも、同じようなオプションがあるかもしれません。