DOBON.NET DOBON.NETプログラミング掲示板過去ログ

丸め誤差の修正について

環境/言語:[WindowsXPhome,.NET 2.0]
分類:[.NET]

小数の丸め誤差の回避方法について質問です。

C#で以下のコードを実行すると

static void Main(string[] args)
{
double d = 0.0;
for (int i = 1; i <= 20; i++)
{
//ここから
d += 0.1;
//ここまでをいろいろといじる
if (d * 10 != i)
{
Console.WriteLine("{0}*10は{1}じゃない", d, i);
}

}
Console.Read();
}

0.3*10は3じゃない
0.8*10は8じゃない
1*10は10じゃない
以下2*10は20じゃないまで続く

と11個の数字が正確に表現できないという結果になります。

これはコンピュータが一部の小数を正確に表現できないことによる丸め誤差の蓄積が問題だと本に書いてありました。

これを回避するために、d += 0.1;の部分を
d = 0.1 * i;
d = Math.Floor((d * 10) + 0.5) / 10;
とすると誤差は少なくなると書いてありました。
実行すると0.1-2の範囲では誤差はなくなったようです。

質問なのですが、上記のコードをいろいろといじって実験してみるとどうやら小数の丸め誤差を回避する方法は上の二行を加える以外にもいろいろあるようなのです。

例えば
d = 0.1 * i;
d = Math.Floor(d * 10) / 10;
でも誤差は出なくなりました。最初の回避策の
d = 0.1 * i;
d = Math.Floor((d * 10) + 0.5) / 10;
の+0.5の部分はどうやらなくてもいいようなのです。
では私が読んだ本ではどうして+0.5があるのでしょうか?

また
d += 0.1;
d = Math.Floor((d * 10) + 0.5) / 10;
でも誤差が出なくなりました。

ここで混乱してしまいました。一体どの方法を採用すればよいのでしょうか?

また、上記のコードは小数第一位を回避できますが、小数第二位の誤差を回避するにはどのような修正をコードに加えればよいでしょうか?
以下のようにやってみましたがだめでした。

static void Main(string[] args)
{
double d = 0.00;
for (int i = 1; i <= 2000; i++)
{
d = i*0.01;
d = Math.Floor((d * 100)) / 100;
if (d * 100 != i)
{
Console.WriteLine("{0}*100は{1}じゃない", d, i);
}

}
Console.Read();

最後に、.NETでの小数にまつわるトラブルはDecimal型を利用すれば完全に回避できるのでしょうか?

よろしくお願いいたしますm_ _m
  • 題名: Re[1]: 丸め誤差の修正について
  • 著者: ガッ
  • 日時: 2006/06/24 22:20:33
  • ID: 16367
  • この記事の返信元:
  • この記事への返信:
    • (なし)
  • ツリーを表示
> 小数の丸め誤差の回避方法について質問です。
とりあえず、有効桁数を決めてしまって、
それを表現できるだけの型を探すところから始めてみては?
「任意の有限桁を完全に表現できる」ものを目指すのなら、それこそ数値演算ライブラリを作るか使うかしかないかと思います。
  • 題名: Re[1]: 丸め誤差の修正について
  • 著者: 名無しぃシャープ
  • 日時: 2006/06/25 0:49:31
  • ID: 16368
  • この記事の返信元:
  • この記事への返信:
    • (なし)
  • ツリーを表示
すなおにdecimalを使ったらどう?
>d = Math.Floor((d * 10) + 0.5) / 10;
>の+0.5の部分はどうやらなくてもいいようなのです。
>では私が読んだ本ではどうして+0.5があるのでしょうか?

上のコードは、小数点第2位で四捨五入を行っています。
例えば、dが1.34286だとすれば、
d * 10は、13.4286
これに0.5を足して、13.9286
そのFloorをとって、13
それを10で割るので、1.3
になります。

なので、

>また、上記のコードは小数第一位を回避できますが、小数第二位の誤差を回避するにはどのような修正をコードに加えればよいでしょうか?

は、もうわかりますよね?

d = Math.Floor((d * 100) + 0.5) / 100;

です。

以上のことと、コンピュータが2進数で計算することによる小数点部の誤差を混乱されているような気がします。.NETには10進数型のDecimalがあります。
DoubleとDecimalの違いについては、以下が参考になると思います。

Double型について
http://www.gdncom.jp/general/bbs/ShowPost.aspx?PostID=29317#29321
こんにちは。

私も気になったので見てみましたが、trapemiyaさんのリンク→じゃんぬねっとさんのリンク→この掲示板(笑)とたどってしまいました。
(まるでねずみの嫁入り状態でした)

しかももう無くなってるし… ○| ̄|_

ということで、いまリンクするならここですね(過去ログ)。
http://dobon.net/vb/bbs/log3-18/10633.html

DOBON.NET | プログラミング道 | プログラミング掲示板