この記事の元ネタはVB.NETやC#に関してのもの。
「どぼん!」さんのサイト「DOBON.NET プログラミング道」で見つけた。
小数(浮動小数点数型)の計算が思った結果にならない理由と解決法、Decimal型はいつ使うか?: .NET Tips: C#, VB.NET
最初、「えっ?こんな桁で間違うのか!?」って思ったけれど、よくよく考えてみれば、そもそもdouble型で真の0.1は表現できないので誤差が出るのは普通だ。
VBAでもやってみた。
Sub test() Debug.Print 0.1 + 0.2 Debug.Print 0.1 + 0.2 = 0.3 End Sub
最初のDebug.Printで0.3と表示されているにもかかわらず、0.3と比較するとFalseになる。
ということは、表示上は0.3でも、本当の0.3になってないということ。
小数点以下何ケタでズレているのか、Round関数を使って確認してみた。
Sub test2() For i = 1 To 22 Debug.Print i; " "; Round(0.1 + 0.2, i) = 0.3 Next End Sub
結果は以下のとおり。
1 True 2 True 3 True 4 True 5 True 6 True 7 True 8 True 9 True 10 True 11 True 12 True 13 True 14 True 15 True 16 True 17 False 18 False 19 False 20 False 21 False 22 False
どうやら17桁目でズレているようだ。
つまり、0.3と表示されてはいるが、0.3000000000000000??????という数値になっているということ。???部は不明
※22までとしたのは、Roundに100まで渡そうとしたところ23でエラーが出たから。
ヘルプには限界値の表記は無かったのでマシン依存かもしれない。
なんでそうなるのかは、「どぼん!」さんのページに解説があるが、こちらでも自分なりに解説してみようと思う。
まず、我々の世界では10進数を用いており、たとえば1025.991という数値は次のように表すことができる。
1の位から左にズレると10倍、100倍(10倍*10倍)となり、右にズレると1/10、1/100(1/10 * 1/10)となる。
値には0~9までの数値が使える。
対してコンピューターが扱うのは2進数である。
1の位から左にズレると2倍、4倍(2倍*2倍)となり、右にズレると半分、1/4(半分の半分)となる。
値には0と1だけが使える。
小数点以下をもう少し見てみると、次のような位になる。
0.5 0.25 0.125 0.0625 0.03125 0.015625 0.0078125 0.00390625 0.001953125
Double型の小数点以下は、これらの数値を組み合わせて表現されている。
2進数なので、使っていいのは各位につき1つのみ。
これらを使って、0.1を作れるか。
…そう、出来ないのだ。
半分の半分の半分の~と位が無限に続くならば、無限に0.1に近づけることは出来、それはもう0.1だとみなして差し支えないだろう。
ただ、コンピューターで扱えるケタは有限なので、どうしても冒頭のような単純な計算でも誤差が生まれてしまう。
実際に0.1が作れないか、試してみよう。
以下のbinの値に小数点以下の二進数を入力していく。
Sub 小数を作ろう() x = 1# bin = "000110011" Summary = 0# For i = 1 To Len(bin) x = x / 2 If Mid(bin, i, 1) = 1 Then Summary = Summary + x End If Next Debug.Print Summary End Sub
上記をそのまま実行すると、0.099609375が表示される。
"0001100111"とすると超えてしまった。→ 0.1005859375
"00011001101"でもまだ超える。→ 0.10009765625
"000110011001"とすると、0.1以下に収まった。→ 0.099853515625
このまま試行錯誤をつづけていくと、最終的に以下の値で表示上は0.1となった。
bin = "000110011001100110011001100110011001100110011001101"
しかし、Debug.Print Summary = 0.1とすると、Falseになる。
このあと2倍ほどの長さまで試したが、完璧な0.1(少なくともコンピューターを騙せるほどの)はとうとう作れなかった。
さて、表示上の0.2も作ってみた。
bin = "001100110011001100110011001100110011001100110011001"
この0.1と0.2の2進数同士を足し算すると、こうなる
bin = "010011001100110011001100110011001100110011001100110"
結果は0.3と表示されるが、やはり0.3と比較するとFalseになる。
小数点以下の計算を正確に行いたい場合は、このブログでも何度か紹介したCDec関数を使う必要がある。
CDecは内部的に整数化して計算していると聞いたことがある。
つまり0.1と0.2がそれぞれ10倍されて1と2になってから足して3になり、再度10分の一にすることで正確な0.3を得る。
これは聞きかじった話なので、間違っているかもしれない。鵜呑みにせず参考程度にとどめて欲しい。
ちなみに、Excel上の計算は賢い。
セルに以下のように入力すると、ちゃんと0.3と一致して○が出力される。
=IF(0.1+0.2=0.3,"○","×")
表計算ソフトと謳って計算を間違えていては話にならない。
ユーザーにとってコンピューターの都合など知ったことではないのだ。
数学的に間違った答えが出てきては困る。そこは内部で計算を工夫しているのだろう。