t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBAでは少数を完璧に計算することが出来ない ~ アドホックな回避策と正しい回避策

VBAでは」と書いたが、これはどの言語にも言える話。

発端は以下のツイート。

実際にやってみた。

Sub test1000()
    Dim d As Double: d = 1
    For i = 1 To 1000
        d = d - 0.001
    Next
    Debug.Print d
End Sub

VBAが出した答えは:-8.81239525796218E-16

数学的には、ゼロになって欲しいところだが、コンピューターは少数を完璧に計算することが出来ない。

そもそも、1から0.1を10回引くだけでも狂う。

Sub test10()
    Dim d As Double: d = 1
    For i = 1 To 10
        d = d - 0.1
        Debug.Print d
    Next
End Sub

ちなみに、アドホック(場当たり的)な解決策

Sub bad_test10()
    Dim d As Double: d = 1
    For i = 1 To 10
        d = d - 0.1
        If i = 10 Then
            Debug.Print 0
        Else
            Debug.Print d
        End If
    Next
End Sub

この例が悪いのは、10回目に失敗することが分かっているのでその10回目だけIfで無理矢理対策している点である。

さて、情報処理を専攻した人にとってコンピューターが小数計算で間違えるというのは常識なのであるが、そうでない方になんでそうなるのか説明するのは難しい。今回は深入りせずに、対策だけさらっと紹介しようと思う。

Currency(通貨)型にすることで精度をあげることはできる。

Sub test1000()
    Dim d As Currency: d = 1
    For i = 1 To 1000
        d = d - 0.001
    Next
    Debug.Print d
End Sub

でも今度は小数点以下が小さくなりすぎると、金額として無視されてしまう。

Sub test10000()
    Dim d As Currency: d = 1
    
    For i = 1 To 10000
        d = d - 0.00001
    Next
    Debug.Print d
End Sub

VBAが出した答えは:1 (減ってない)

回避策としてはCDec関数を使って計算の途中をDecimal型にしてやること。
以下のマクロは繰り返し回数が多いので私のPCでは5秒くらいかかったが、結果は問題なく0になる。

Sub test10000000()
    Dim d As Double: d = 1
    
    For i = 1 To 10000000
        d = CDec(d - 0.0000001)
    Next
    Debug.Print d
End Sub

ただし、Decimal型はその分メモリを使うので、誤差が発生しうる場合のみに使う。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。