会議室で定期的に登場する問題です。なぜそうなるかを理解していない人が一定数存在します。理系でなくてもコンピュータ系の学校では教えているんですがね。会社のOJTで欠落するのかな?
浮動小数点演算の有効桁に依存する誤差と変数の型に依存する表現誤差かあるので、難しく考えてしまうのでしょうか?
有効桁数の話は初等数学で登場するので省略します。ご承知のように数字変数の型には整数型・固定小数点型・浮動小数点型がありました。
これらには二進数で表現できない数 (2 ^ n)は保持できないわけで、代入した時点で精度が落ちます。
桁落ちしにくい保持の方法には二進化十進(BCD)・パック10進数・ゾーン10進数・3増し符号・固定小数点数などがあります。汎用機(COBOL)ではPack/Zoneが使われてますね。昔80系アセンブラが全盛だったころ( <-いつだ)BCDで悪戦苦闘した記憶があります。MSの世界では、COleCurrencyが固定小数点と称していましたが、.netではDecimal型になってます。固定小数点ではなく固定長の有効桁数と小数点部桁数を持つ数値型と称し
てます。固定小数点型は System.Data.SqlTypes.SqlMoney型がありますが、標準では何故か無いんでよね。
Decimal型は固定小数点だから誤差は生じないと誤解している人が少なからずいます。Decimal型といえども二進表現で保持するので誤差が生じることがあります。Decimal型は固定小数点ではありません。もちろん純粋の固定小数点でも除算による誤差は生じます。実演して説明しようとして墓穴を掘っちゃいました。
電卓で 1 / 3 の後 * 3 をしたら 0.99999999 になりますね。( 1になる電卓もあるみたいですね。)
ではプログラムで
Dim da As Decimal = 1
Dim db As Decimal = (da / 3)
Dim dc As Decimal = db * 3
Dim fa As Double = 1
Dim fb As Double = (fa / 3)
Dim fc As Double = fb * 3
MessageBox.Show(dc.ToString + " : " + fc.ToString)
結果は共に "0.999・・ : 0.999・・" を期待していたのですが、意に反して "0.9999999999999999999999999999 : 1"でした。 ( doubleの結果こそ 0.999・・であって欲しかったのに。最終桁の足しこみ誤差が繰り上がって結果として1.0になったのでしょうか。)
「このように異なった結果になります。Decimalでも期待と違った結果になりますね。」・・・誤魔化しちゃった。.wwwwww)
気を取り直して
Dim da As Decimal = 1
Dim db As Decimal = 0.01D
Dim dc As Decimal = 0
For i As Integer = 0 To 99
dc += db
Next
Dim fa As Single = 1
Dim fb As Single = 0.01
Dim fc As Single = 0
For i As Integer = 0 To 99
fc += fb
Next
Dim ta As Double = 1
Dim tb As Double = 0.01
Dim tc As Double = 0
For i As Integer = 0 To 99
tc += tb
Next
これも、すべて 0.999・・・を期待してたのですが、結果は "1.00 : 0.9999993 : 1" でした。
「DecimalとDoubleは1になりますが、singleは1になりませんね。」・・・再び誤魔化しちゃった。(なぜだろう..未解決)
このように小数点演算は誤差を伴います。型の特性をよく理解して使用してください。Decimal型と言えども同じです。(強引に結論)
起こって欲しい時に起こらないマーフィー則なのかな。<---事前準備がなってないだけだ。