調べてみるとMath.Round()は、四捨五入ではなく近似値を表示するようだ。
といっても実際のところMath.Round()が原因というよりは、double型の誤差が原因だった。
Round.Math(double)
以下のコードは、double型の変数に0.1ずつ足して、Math.Round()の結果を表示している。
double num1 = 11.1;
for (int i = 0; i < 9; i++)
{
Console.WriteLine("{0} -> {1}", num1, Math.Round(num1));
num1 += 0.1;
}
この場合、以下の様な結果になる。
- 11.1 –> 11
- 11.2 –> 11
- 11.3 –> 11
- 11.4 –> 11
- 11.5 –> 11
- 11.6 –> 12
- 11.7 –> 12
- 11.8 –> 12
- 11.9 –> 12
「11.5」が「11」に、「11.6」が「12」となり、「五捨六入」になってしまっている。
デバッグで変数の中身を追っていくと、以下のように誤差が生まれてしまう。
- 11.1
- 11.2
- 11.299999999999999
- 11.399999999999999
- 11.499999999999998
- 11.599999999999998
- 11.699999999999998
- 11.799999999999997
- 11.899999999999997
MSDNの注意書きにあるように、double型の精度が低下が、Math.Round()で四捨五入できない原因だ。
呼び出し時の注意
10 進値を浮動小数点数として表したり、浮動小数点値で算術演算を実行したりすると、精度が低下する可能性があるため、Round(Double) メソッドで、中間値を最も近い偶数の整数に丸められない場合があります。
Math.Round メソッド (Double) (System)
では、誤差をなくすにはどうすればよいか?
それは、decimal型を使えば良い。
Round.Math(decimal)
decimal num2 = 11.1M;
for (int i = 0; i < 9; i++)
{
Console.WriteLine("{0} -> {1}", num2, Math.Round(num2));
num2 += 0.1M;
}
decimal型を使った場合の結果は、以下の通り。
- 11.1 –> 11
- 11.2 –> 11
- 11.3 –> 11
- 11.4 –> 11
- 11.5 –> 12
- 11.6 –> 12
- 11.7 –> 12
- 11.8 –> 12
- 11.9 –> 12
デバッグで変数の中身を追っても、誤差なく加算されている。
- 11.1
- 11.2
- 11.3
- 11.4
- 11.5
- 11.6
- 11.7
- 11.8
- 11.9
さいごに
とくに金額を扱うプログラムの場合は、必ずdecimal型を使うようにしてください。
1円の誤差が大問題になる可能性があります。(特に税金がらみで)
あと忘れがちなのは、単純に数値「11.1」と書いてもdecimal型にはなりません。
decimal型を使う場合は、「11.1M」と「M」を忘れず書くようにしてください。
以上
written by @bc_rikko
0 件のコメント :
コメントを投稿