C#中的实数不一致舍入实数

时间:2013-08-06 23:05:47

标签: c# .net

我有这个测试代码:

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?

Test class output

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

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

提前致谢。

3 个答案:

答案 0 :(得分:9)

不像其他两个答案一样以不同方式阅读您的问题。它的要点: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,99999999999999989”。

前者总是使用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位有效数字的往返次数。换句话说,double的{​​{1}}表示与1.00...07的表示相同。

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

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

G17

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

答案 1 :(得分:5)

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

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

答案 2 :(得分:3)

众所周知,1/6 = 0.1666(重复),decimaldouble 不能代表重复数 ,它们是经过计算的当分配给。由于它们是根据不同的后备数据结构构建的,因此它们代表了一组不同的可能数字,并且在某些情况下有所不同。

对于此代码:

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

由于notQuiteWholeD0.99999999999999989,因此打印为真。

我不打算介绍doubledecimal如何在幕后工作,但如果你有兴趣,这里有一些阅读材料。