将十进制数转换为双精度浮点数时出现奇怪的行为

5

我在将十进制数转换为双精度浮点数时遇到了奇怪的问题。

以下代码返回 true:

Math.Round(0.010000000312312m, 2) == 0.01m //true

然而,当我将其转换为双精度浮点数时,它返回false:
(double)Math.Round(0.010000000312312m, 2) == (double)0.01m //false

当我想使用Math.Pow时,我遇到了这个问题,因为没有针对decimal的Math.Pow重载,所以被迫将其强制转换为double。

这是已知的行为吗?当我被迫将decimal强制转换为double时,我该如何避免这种情况?

来自Visual Studio的屏幕截图:

Visual Studio的屏幕截图

将Math.Round强制转换为double后,我得到了以下结果:

(double)Math.Round(0.010000000312312m, 2)   0.0099999997764825821   double
(double)0.01m   0.01    double

更新

好的,我按照以下步骤复现了该问题:

  1. 当我运行WPF应用程序并在启动后立即检查输出时,我会得到与空项目相同的true值。
  2. 应用程序的一部分将滑动条的值发送到计算算法中。我得到错误的结果,并在计算方法上设置断点。现在,当我在观察窗口中检查值时,我得到false值(没有进行任何修改,只是刷新观察窗口)。
  3. 只要我在某个较小的项目中复现了该问题,我就会在此处发布它。

更新2

不幸的是,我无法在较小的项目中复现该问题。我认为Eric的答案解释了原因。


1
你的第二个语句也返回了 true - Rotem
3
不应该对双精度数使用等号检查,而应该使用一些 Epsilon 值。 - ne2dmar
@Rotem:在vs2012,.net 4.0上返回false。 - empi
请查看以下链接。Double和Decimal并不总是精确的。http://msdn.microsoft.com/en-us/library/system.double.aspx#Precision http://msdn.microsoft.com/en-us/library/364x0z75.aspx - paparazzo
@Blam 这怎么解释只有 OP 得到 false 而其他人得到 true - Rotem
显示剩余5条评论
2个回答

12

评论中有人报告说,有时比较结果是true,有时是false。

不幸的是,这是可以预料到的。C#编译器、JIT和CPU都被允许对双精度浮点数进行算术运算,精度超过64位双精度浮点数,取决于它们的实现方式。这意味着有时候看起来“相同”的计算结果可能在一次计算中以64位精度完成,在另一个计算中则以80或128位精度完成,两个结果的最后一位可能会不同。

让我确保您理解我所说的“根据他们自己的判断”。您可能因任何原因而得到不同的结果。您可以在调试和发布中获得不同的结果。如果您让编译器在常量中进行计算,并使运行时在运行时进行计算,您可能会得到不同的结果。当调试器正在运行时,您可能会得到不同的结果。您可能会在运行时和调试器的表达式求值器中获得不同的结果。任何原因。双精度浮点数算术是本质上不可靠的。这是由于浮点芯片的设计;这些芯片上的双精度浮点数算术无法在没有相当大的性能损失的情况下变得更加可重复。

出于这个原因您几乎不应该精确比较两个双精度浮点数。相反,应该计算它们之间的差值,并查看差值的绝对值是否小于一个合理的界限。

此外,了解将双精度浮点数四舍五入为两位小数是困难的原因非常重要。一个非零有限的双精度浮点数是一个形式为(1 + f)× 2 e 的数字,其中f是分母为2的幂的分数,e是指数。显然,无法以这种形式表示0.01,因为无法从等于2的幂次方的分母中获得一个等于10的幂次方的分母。

双精度浮点数0.01实际上是二进制数1.0100011110101110000101000111101011100001010001111011x2-7,在十进制中为0.01000000000000000020816681711721685132943093776702880859375。这是双精度浮点数中可能最接近0.01的值。如果需要表示确切的值,则使用十进制。这就是为什么叫做十进制的原因。
顺便提一下,我在StackOverflow上回答过类似的问题很多次。例如: 为什么在 C# 中使用括号和语句分开时,浮点精度会有所不同? 此外,如果需要“拆分”双精度浮点数以查看其位,请使用我之前编写的这个有用代码。它需要安装Solver Foundation,但那是免费下载的。 http://ericlippert.com/2011/02/17/looking-inside-a-double/

0

这是有文档记录的行为。十进制数据类型比双精度类型更精确。因此,当你从十进制转换为双精度时,可能会有数据丢失的可能性。这就是为什么需要进行显式类型转换的原因。

有关更多信息,请参阅以下 MSDN C# 参考:

十进制数据类型:http://msdn.microsoft.com/en-us/library/364x0z75(v=vs.110).aspx

双精度数据类型:http://msdn.microsoft.com/en-us/library/678hzkk9(v=vs.110).aspx

强制转换和类型转换:http://msdn.microsoft.com/en-us/library/ms173105.aspx


2
这个解释并没有解释上面评论中显示的结果,即有时结果为真,有时结果为假。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接