【问题标题】:Math.Round() yields unexpected result for doubleMath.Round() 为 double 产生了意外的结果
【发布时间】:2014-08-21 17:28:50
【问题描述】:

我偶然发现我的代码中的一个方法在我的代码中计算了一个四舍五入的值错误。 我知道比较双精度值会产生意外结果的问题。

例子

    double x = 19.08;
    double y = 2.01;
    double result = 21.09;

    if (x + y == result)
    {
        // this is never reached
    }

此处解释:http://csharpindepth.com/Articles/General/FloatingPoint.aspx

但是,到目前为止,我希望 Math.Round() 方法即使使用双精度值也是准确的。

看看这段代码。

        var decimals = 2;
        var value1 = 4.725;
        var value2 = 4.725M;

        var result1 = Math.Round(value1, decimals, MidpointRounding.ToEven);
        var result2 = Math.Round(value1, decimals, MidpointRounding.AwayFromZero);
        var result3 = Math.Round(value2, decimals, MidpointRounding.ToEven);
        var result4 = Math.Round(value2, decimals, MidpointRounding.AwayFromZero);

        Console.WriteLine("Double (ToEven): {0}", result1); // outputs 4.72
        Console.WriteLine("Double (AwayFromZero): {0}", result2); // outputs 4.72 (expected: 4.73)
        Console.WriteLine("Decimal (ToEven): {0}", result3); // outputs 4.72
        Console.WriteLine("Decimal (AwayFromZero): {0}", result4); // outputs 4.73

对我来说,result2 应该是 4.73 是完全清楚的。然而,事实并非如此。 谁能解释一下原因?

【问题讨论】:

  • 结果4真的输出4.72吗?
  • 结果 4 显示 4.73。顺其自然,但 double 听起来有点奇怪。
  • 拉吉,绝对不是。实际上,这正是您所期望的。
  • 抱歉,复制粘贴 ;) 结果 4 是 4.73,如预期的那样

标签: c# .net double


【解决方案1】:

好吧,您可能需要重新考虑“完全清楚”的概念,因为 4.725(相对于 4.625)不能double 精确表示。其实就是这样

4.7249999999999996447286321199499070644378662109375

请记住,浮点数只是对实数的数学概念的近似——您对数字应该如何表现的许多直观概念并不适用。您最终会得到一个 大约 4.725 但显然略低于它的值。因此,中点舍入模式在这里不会做任何事情,因为它并不完全位于两个可能要舍入的数字之间。

【讨论】:

  • +1 对我来说是最后一句话,这是最好的答案
  • +1。如果 OP 对为什么这是 4.7249999999999996 感到困惑,他们应该考虑将 1/3 精确地写为十进制数。他们当然不能,他们从0.3333333333 开始,一直到他们累了或空间用完为止。 double 对二进制的4725/1000 和十进制的1/3 做同样的事情。这样做是最好的,但在4.7249999999999996447286321199499070644378662109375 之后空间不足。
  • 是的,有些我们可以使用更大的数据类型来完全精确(如果我们没有至少 5 位尾数,我们也不能在十进制中精确到 12345/10000),无论大小如何,有些都是不可能的 - 就像十进制的 1/3。
  • 我知道并不是每个十进制数都可以用双精度数精确表示。我的问题更像是Why does Math.Round(...) 不考虑这个?因为,如果我将我的 double 转换为十进制并执行Math.Round(...),结果是 4.73。
  • @SchlaWiener 对round 的调用不知道您输入了4.72,它知道您要对4.7249999999999996447286321199499070644378662109375 进行舍入。对于所有方法都知道,您输入了它的每个数字(或从另一个来源获取它的每个数字)。不然怎么知道。
【解决方案2】:

您的value1 很容易成为4.724999999999999999999999999999999。为什么要四舍五入到4.73而不是4.72

【讨论】:

    【解决方案3】:

    在检查 Double 值是否相等时总是会出现问题。添加两个像 0.5 + 0.5 这样的双精度值不会是 1.0,但很可能最终会是 0.99999 和 1.00001。这与浮点数从二进制到十进制的转换有关。

    您应该在舍入误差/精度部分检查以下读数: http://www.learncpp.com/cpp-tutorial/25-floating-point-numbers/ 它为浮点精度提供了一些有价值的见解,C# 也应该遵循这种情况。

    【讨论】:

      【解决方案4】:

      使用 Convert.ToDouble(value1.ToString("f2")) 代替 Math.Round(value1, 2)

      【讨论】:

      • 那不一样。您对 ToString() 如何舍入您的值没有影响。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-03
      • 1970-01-01
      • 2020-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多