6

I am using Visual Studio Professional 2012. I created a new C# ConsoleApplication, targeting .NET Framework 4.5, with following code:

    static void Main(string[] args)
    {
        double x = 2.44445;
        double y = Math.Round(x, 4, MidpointRounding.AwayFromZero);
        Console.WriteLine(y);
        Console.ReadKey();
    }

The expected result should be 2.4445, but it actually returns 2.4444. //Same result with previous framework version, and I tried VCE2010.

I know such problem usually results from the way double data type is stored (i.e. finite decimals converted to infinite binary fraction). But I didn't expect this to happen with only 5 decimal digits like 2.44445

I'm worrying if such thing could happen with even shorter decimals. I would also like to learn a safer way to round (using away from zero convention) in C#. Thanks.

4

2 回答 2

11

这确实是由于浮点数的脆弱精度。0.5 可以完美地存储在 IEEE 浮点中,但 0.45、0.445 等不能。例如,当您指定 2.44445 时存储的实际值是 11009049289107177/4503599627370496,即 2.44449999999999989519494647... 现在应该很明显为什么该数字按原样四舍五入了。

如果您需要精确存储小数,请考虑改用该decimal类型。

于 2012-12-27T13:45:09.787 回答
4

来自 msdn 的注释

由于将十进制值表示为浮点数或对浮点值执行算术运算可能导致精度损失,因此在某些情况下,Round(Double, Int32, MidpointRounding) 方法可能不会按照指定的方式舍入中点值通过模式参数。这在以下示例中进行了说明,其中 2.135 舍入为 2.13 而不是 2.14。发生这种情况是因为该方法在内部将值乘以 10 位,并且这种情况下的乘法运算会损失精度

这就是 Round 的实现方式:

double num = roundPower10Double[digits];
value *= num;
if (mode == MidpointRounding.AwayFromZero)
{
    double num2 = SplitFractionDouble(&value);
    if (Abs(num2) >= 0.5)
    {
        value += Sign(num2);
    }
}

如您所见,value 乘以 num,它是来自的值

roundPower10Double = new double[] { 1.0, 10.0, 100.0, 1000.0, 10000.0, 
      100000.0, 1000000.0, 10000000.0, 100000000.0, 1000000000.0, 10000000000,  
      100000000000, 1000000000000, 10000000000000, 100000000000000, 1E+15 };    

所以,实际上你有2.44445 * 10000.0 - 24444,0which 给0,499999999996362. 小于0.5。这样你就有了2.4444

于 2012-12-27T13:46:59.710 回答