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

我有这个测试代码:

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 

这一个,为什么打印真实?

测试类输出

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

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

提前致谢。

不像其他两个答案那样以同样的方式阅读你的问题。 它的要点:C#中double “圆”的格式化字符串表示forms?

是。

内部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位有效数字的往返次数。 换句话说, 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" 

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

Decimal以基数10存储。 Double数以基数2存储。这些基数中的任何一个都不能用有限表示精确地表示1/6。

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

我们知道,1/6 = 0.1666(重复), decimaldouble 不能表示重复数 ,它们在分配时计算。 由于它们是由不同的后备数据结构构建的,因此它们代表了一组不同的可能数字,并且在某些情况下不同。

对于此代码:

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

由于notQuiteWholeD0.99999999999999989因此打印为true。

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

  • 双精度浮点格式
  • 那么 - 在幕后,C#/ .NET中十进制值类型发生了什么?
  • .NET中的十进制浮点数