hnwの日記

C#のdecimalからdoubleへのキャストにバグを見つけた気がする

C#の丸めは基本的に偶数丸め(banker's rounding)だというのが僕の認識ですが、decimalの数をdoubleにキャストするときに四捨五入になる例がありました。また、.NET環境に限り最近接のdoubleに丸められない例も見つけました。

decimalからdoubleへのキャストは偶数丸めでなく四捨五入になる

まずは次の例を見てみましょう。これは.NET、Mono環境ともに同じ結果になります。

using System;

class DecimalTest1
{
    static void Main()
    {
        double x1 = 5000000000000000.5; // 偶数丸めで5000000000000000になる
        decimal y = 5000000000000000.5m;
        double x2 = (double)y; // 四捨五入されて5000000000000001になる

        Console.WriteLine(x1 - x2); // -1
    }
}


上記サンプルプログラム中の巨大な数は2^52以上2^53以下の数です。この範囲の数はdoubleでは1.0刻みでしか表現できません。上記の例ではdoubleでピッタリ表現できない数を浮動小数リテラルとして記述しているので丸めが起こりますが、この数はちょうど「ど真ん中」なので偶数丸めになり、この場合だと切り捨てられます。これは多くの処理系と共通の挙動になります。


一方、この数は10進で17桁しか無いのでdecimalではピッタリ表現できます。ところが、このdecimalの数をdoubleにキャストすると偶数丸めにならず、いわゆる四捨五入による切り上げが起こってしまいます。

続きを読む