re:0.01を100回加算しても1にならない?それともなる?
きのうのエントリへの自己フォロー。ちょっとテストPを書いてみた。自分に課した制限時間は10分! がんばれ俺!
Module Module1 Sub Main() Dim denominator As Decimal = 1 Dim slicednumber As Single Dim sum As Single = 1 Do While sum = 1 '1をdenominatorで割ったものを、denominator分だけ繰り返し加算したものが1になる間 denominator += 1 'denominatorを1づつ増やしながらどこで誤差を検知するのかを調べる。1は自明なのでその次から。 slicednumber = 1 / denominator sum = 0 For cnt As Decimal = 1 To denominator '割った数だけ割った値を繰り返し加算するループ sum += slicednumber Next Loop System.Console.WriteLine("An error detected when denominator is {0}", denominator) System.Console.WriteLine("sum is {0}", sum.ToString("e10")) End Sub End Module
ランゲージはVisual Basic 2008。英語のつづりと書式指定がわかんなくて8分ぐらいかかった(遅)。実行結果は
An error detected when denominator is 10 sum is 1.0000001200e+000
正解は「ならない!」というか0.1でダメかよ!
やはり二進数の丸めは侮れない。でも3で割った時点で有限桁で実数を表現しようとした時のアンダーフローが真っ先に発生する。Singleでそれを検知できないのは精度が悪すぎるから。
ちなみにSingleをDoubleにすると6でだめになる。
An error detected when denominator is 6 sum is 1.00000000000000020000e+000*1
一瞬ええっ!っと思うけど、誤差を検知できるだけ精度が向上することによって手前でストップするようになるので、これで合っている。まあ、1/6は循環小数なのでおかしくなるのもあたりまえ。じゃあ、10進数で絶対割り切れている10^−nだとどうなるか? 精度をDoubleのままちょっとプログラムを修正*2して実行すると。
An error detected when denominator is 10 sum is 9.99999999999999890000e-001
それでも1/10、0.1が正確に表現できないことが判明。なんというボロさか! デジタルコンピュータは0.1さえも正確に表現できない!!!!
と、世をはかなんで死のうかと思ったけど、実は.NET Frameworkにはこの辺の問題に関しては最強の型がある。Decimal。
今日この調査をやるまではDecimalは単なる整数型だと思い込んでいた。半分は当たっているんだけど半分は間違いで、確かに内部表現、浮動小数点数の仮数部にあたる部分が96ビットある整数なんだけど、その整数の小数点の位置が自由自在に変更できるという特徴がある。小数の演算でも、小数点の位置を右端まで移動したときに、その値が(2^96)−1までの範囲内で表現できるなら実質整数の演算になるので誤差は生じないということになる。オーバーフローするなら終了だけど。
いや、ほんと、金融の数理計算にはCOBOLが最強だと改めて実感。というか、なんで一番基本の型としてそーゆーDecimalみたいなのが普及していないんだ? JavaとかDecimalみたいな型あるんだっけ?? 明確な目的意識を持って設計されたCOBOLというものに、いや、COBOLを作った人たちに畏敬の念を感じた日だった。