
演算誤差について(10進数と2進数) (unibon)
2000年03月05日: 新規作成(推定)。
2002年09月01日: ボタンを押した時に出るメッセージの内容が間違っていたので訂正。0.0021 の2進数表記を追加。
10進数と2進数の相互変換にまつわる演算誤差について。
コンピュータができた時からの永遠の課題として、10進数と2進数にまつわる誤差の問題があります。
ただ、誤差があるといっても僅かなものなので、普通に科学技術計算をするような場合は、単に有効桁数に注意すれば良い程度であり、さほど問題とはなりません。もちろん問題になる場合もいろいろとはあり、計算を繰り返すごとに誤差が累積するなどという問題や、誤差の再現性についてなどありますが、ここでは触れないこととします。
今回、問題とするのは、いわゆる事務計算をする際の誤差についてです。
いきなりですが、実例としては、手数料の計算をする場合を考えます。
ある商品の売買に関する契約として、
「商品の売買に関する手数料は、その商品定価の 0.21% とし、端数は切り捨てる。」
という条件があった場合、定価が
29999 円
30000 円
30001 円
の 3 通りものについて考えてみます。
電卓で計算すると、
29999 円 × 0.0021 = 62.9979 円 なので切り捨てると 62 円
30000 円 × 0.0021 = 63 円 なので切り捨てるまでもなく 63 円
30001 円 × 0.0021 = 63.0021 円 なので切り捨てると 63 円
となります。
しかし、(電卓以外の)コンピュータで計算すると、
29999 円 の場合は 62 円
30000 円 の場合、なぜか 62 円
30001 円 の場合は 63 円
となります。
これは、突き詰めると 0.0021 という値が有限の桁数の 2 進数で表現しきれないためです。
0.0021 を10進数と2進数で表現すると、
0.0021 (10 進数の小数表現) = 21 / 10000 (10進数の分数表現) = 10101 / 10011100010000 (2進数の分数表現) = 0.0000000010001001101000000010011101010010010101000110000010101(2 進数の小数表現)
のように、どうしても無限に続く小数(循環小数になるはず)となってしまいます。それをある桁数で打ち切ってしまうために、真の値よりも大きくなるか小さくなるかのどちらかになってしまいます。
したがって a × 0.0021 = c となる際に、たまたま真の値で計算した場合の c が整数となる場合は、
実際には 0.0021 は真の値よりも大きいか小さいかのいずれかなので、
c が真の値よりも大きいか小さいかのいずれかになってしまい、
「切り捨て」という操作により、これが大きく結果を歪めてしまいます。
これを解決するには、以下のいずれかの方法があります。
-
2 進数で表現できない値をコード中に持たない
これは、a * 0.0021 というコードは持たずに、
a * 21 / 10000 に置きかえるやりかたです。
これも a * (21 / 10000) としてしまうとだめであり、
(a * 21) / 10000
とする必要があります(演算は左からおこなわれるため、カッコは必須ではありませんが)。
-
10 進数を考慮したモジュールを使う。
これは VBScript だと Currency 型を使うことで実現できます。コード上では CCur 関数を使うことになります。
ただ、有効桁数が多少物足りなく、小数点以下の桁数が 4 桁です。また、JavaScript にはこれに相当する機能はありません。
-
JavaScript で演算結果の誤差を目立たなくする パート 2 のような手法を使う。
-
VBScript での演算誤差とその対処について のような手法を使う。この例はさきほどの3つ目のボタンで動かせます。
読み物の目次
ホーム
(このページ自身の絶対的な URL)