VBScript での演算誤差とその対処について (unibon)

2002年09月01日: 新規作成。
VBScript での演算誤差に関する話題として、演算の繰り返しによる誤差の累積を避けるやりかたについて。

0.0001 を 10000 回加算すると 1 になるか

普通にやるとこんな感じです。

Option Explicit
Dim s
s = 0
Dim i
For i = 0 To 10000 - 1
    s = s + 0.0001
Next
Call window.alert("s = " & s)
結果は 0.999999999999906 になります。

つぎにこれに対処したやりかたです。

Option Explicit
Dim s
s = 0
Dim i
For i = 0 To 10000 - 1
    s = CDbl(CStr(s + 0.0001))
Next
Call window.alert("s = " & s)
結果は 1 になります。

これはどういう仕組みかというと、VBScript(および Visual Basic)は内部の2進数浮動小数点の値を10進数の文字列に変換する際に、末尾の桁を丸めて端数が出にくいようにしているためです。
ループの中で、加算処理をするたびに毎回明示的に CStr を経由することにより、数値型が文字列型に変換され、この際にこの丸めが実行されて10進数での端数がなくなくなるような補正がかかっています。この補正を流用して、誤差が累積することを不正でいます。
ただし、このやりかたには良いことばかりではなく、結局は精度が損なわれているため、複雑な数値計算をおこなった結果を文字列として表現する際に、理論値よりも精度が悪くなってしまうので、そのような用途には好ましくありません。あくまでも10進数で表記した場合に有限の桁数で表示されるはずだということを前提条件にできる場合にだけ使える手法です。

ちなみに JavaScript では、この技は使えません。ただし精度は損なわれていません。

var s = 0;
for (var i = 0; i < 10000; i++) {
    s = s + 0.0001;
}
window.alert("s = " + s);
結果は 0.9999999999999062 になります。

JavaScript でも VBScript と同等の補正のやりかたをおこなっても、JavaScript は丸めてくれません。

var s = 0;
for (var i = 0; i < 10000; i++) {
    s = ("" + (s + 0.0001)) - 0;
}
window.alert("s = " + s);
これも結果は 0.9999999999999062 であり、1 にはなりません。

ASP の目次
ホーム
(このページ自身の絶対的な URL)