【问题标题】:Inconsistency rounding real numbers in C #C#中实数四舍五入不一致
【发布时间】:2013-08-06 23:05:47
【问题描述】:

我有这个测试代码:

class Test
{
    static void Main()
    {
        decimal m = 1M / 6M;
        double d = 1.0 / 6.0;

        decimal notQuiteWholeM = m + m + m + m + m + m; // 1.0000000000000000000000000002M
        double notQuiteWholeD = d + d + d + d + d + d;   // 0.99999999999999989

        Console.WriteLine(notQuiteWholeM); // Prints: 1.0000000000000000000000000002
        Console.WriteLine(notQuiteWholeD); // Prints: 1.

        Console.WriteLine(notQuiteWholeM == 1M);  // False
        Console.WriteLine(notQuiteWholeD < 1.0);   // Prints: True. Why?

        Console.ReadKey();
    }
}

为什么这行打印 1?

Console.WriteLine(notQuiteWholeD); // Prints: 1

一个这个,为什么打印True?

是否有自动舍入过程?如何打印正确/计算的值?

[注意:我在 C# 5.0 in a Nutsheel 第 30 页:实数舍入错误] 中找到了此示例代码。

提前致谢。

【问题讨论】:

标签: c# .net


【解决方案1】:

与其他两个答案的阅读方式不同。它的要点:在 C# 中 double "round" 的格式化字符串表示是否?

是的。

在内部 double 以完整的 IEEE-754 十进制数字精度(15-17 位)表示,这就是为什么:

notQuiteWholeD < 1.0 == true    // because notQuiteWholeD = 0.99999999999999989

然而,当将其格式化为字符串时,默认情况下它将使用 15 位精度 - 相当于:

String.Format("{0:G15}", notQuiteWholeD)   // outputs "1"

要获取完整内部表示的所有数字,您可以使用:

Console.WriteLine("{0:G17}", notQuiteWholeD);

或者:

Console.WriteLine("{0:R}", notQuiteWholeD);

在这种情况下,两者都将输出“0,999999999999999989”。

前者将始终使用 17 位精度。后者(“往返精度”)将使用 15 位数字,如果精度足以满足以下要求,则将使用 17 位:

Double.Parse(String.Format("{0:G15}", notQuiteWholeD)) == notQuiteWholeD

奖励示例: ...当G17R 不同时:

Console.WriteLine("{0:G17}", 1.0000000000000699); // outputs "1.0000000000000699"
Console.WriteLine("{0:R}",   1.0000000000000699); // outputs "1.00000000000007"

1.0000000000000699(17 位有效数字)可以足够准确地表示,仅使用 15 位有效数字即可实现往返。换句话说,1.00...07double 表示与 1.00...0699 的表示相同。

所以1.00...07(15 位)是一个较短的输入,以获得完全相同的内部(17 位)表示。这意味着R 会将其四舍五入为 15 位,而G17 将保留内部表示的所有数字。

当意识到这一点时可能会更清楚:

Console.WriteLine("{0:G17}", 1.00000000000007); // outputs "1.0000000000000699"
Console.WriteLine("{0:R}",   1.00000000000007); // outputs "1.00000000000007"

...给出完全相同的结果。

【讨论】:

    【解决方案2】:

    Decimal 以基数 10 存储。Double 以基数 2 存储。这些基数都不能用有限的表示形式精确表示 1 / 6。

    这解释了除Console.WriteLine(notQuiteWholeD) 之外的所有输出。即使存储的实际值小于 1,输出也会得到“1”。由于输出以 10 为基数,因此必须从 2 基数转换。部分转换包括舍入。

    【讨论】:

      【解决方案3】:

      众所周知,1/6 = 0.1666(重复),decimaldouble 不能表示重复的数字,它们是在赋值时计算出来的。由于它们是由不同的支持数据结构构建的,它们代表一组不同的可能数字,并且在某些情况下以不同的方式舍入。

      对于此代码:

      Console.WriteLine(notQuiteWholeD < 1.0);   // Prints: True. Why?
      

      因为notQuiteWholeD0.99999999999999989,所以它打印为true。

      我不会介绍 doubledecimal 在幕后的工作原理,但如果您有兴趣,这里有一些阅读材料。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-26
        • 2015-11-12
        • 2023-01-12
        • 1970-01-01
        相关资源
        最近更新 更多