2014/10/18

【C#】Math.Round()が四捨五入にならない!

四捨五入した結果が欲しいのに、なぜかうまく計算されずハマってしまった。
調べてみると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;
}


この場合、以下の様な結果になる。
  1. 11.1 –> 11
  2. 11.2 –> 11
  3. 11.3 –> 11
  4. 11.4 –> 11
  5. 11.5 –> 11
  6. 11.6 –> 12
  7. 11.7 –> 12
  8. 11.8 –> 12
  9. 11.9 –> 12

「11.5」が「11」に、「11.6」が「12」となり、「五捨六入」になってしまっている。


デバッグで変数の中身を追っていくと、以下のように誤差が生まれてしまう。
  1. 11.1
  2. 11.2
  3. 11.299999999999999
  4. 11.399999999999999
  5. 11.499999999999998
  6. 11.599999999999998
  7. 11.699999999999998
  8. 11.799999999999997
  9. 11.899999999999997


呼び出し時の注意

10 進値を浮動小数点数として表したり、浮動小数点値で算術演算を実行したりすると、精度が低下する可能性があるため、Round(Double) メソッドで、中間値を最も近い偶数の整数に丸められない場合があります。

Math.Round メソッド (Double) (System)
MSDNの注意書きにあるように、double型の精度が低下が、Math.Round()で四捨五入できない原因だ。

では、誤差をなくすにはどうすればよいか?
それは、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型を使った場合の結果は、以下の通り。
  1. 11.1 –> 11
  2. 11.2 –> 11
  3. 11.3 –> 11
  4. 11.4 –> 11
  5. 11.5 –> 12
  6. 11.6 –> 12
  7. 11.7 –> 12
  8. 11.8 –> 12
  9. 11.9 –> 12
ちゃんと思い通りに四捨五入されている。

デバッグで変数の中身を追っても、誤差なく加算されている。
  1. 11.1
  2. 11.2
  3. 11.3
  4. 11.4
  5. 11.5
  6. 11.6
  7. 11.7
  8. 11.8
  9. 11.9



さいごに



とくに金額を扱うプログラムの場合は、必ずdecimal型を使うようにしてください。
1円の誤差が大問題になる可能性があります。(特に税金がらみで)

あと忘れがちなのは、単純に数値「11.1」と書いてもdecimal型にはなりません。
decimal型を使う場合は、「11.1M」と「M」を忘れず書くようにしてください。



以上

written by @bc_rikko

0 件のコメント :

コメントを投稿