c#和javascript中的IEEE 754浮点运算舍入误差

IEEE 754 floating point arithmetic rounding error in c# and javascript

我刚刚读了一本关于javascript的书。作者在IEEE754标准中提到了浮点算术舍入误差。

例如,添加0.1和0.2将生成0.300000000004,而不是0.3。

所以(0.1 + 0.2) == 0.3返回false。

我也在C中复制了这个错误。

所以我的问题是:

此错误多久发生一次?C和JavaScript中的最佳实践解决方案是什么?哪些其他语言有相同的错误?


这不是语言上的错误。在IEEE754中这不是一个错误。这是对二进制浮点数的期望和使用上的错误。一旦你理解了二进制浮点数的真正含义,它就完全有意义了。

c中的最佳实践是使用十进制浮点类型的System.Decimal(aka decimal),无论何时处理自然以十进制(通常是货币值)表示的数量。

有关更多信息,请参阅我关于.NET二进制浮点和十进制浮点的文章。


这三个数字在双精度浮点中的最接近表示形式是:

  • 0.1——>0.100000000000000001=D(3FB99999 999999 A)
  • 0.2——>0.2000000000001=D(3fc99999 999999 A)
  • 0.3——>0.2999999999999=D(3FD333333 33333333)

超过0.2999999999999的下一个较大的可表示数字是:

  • 0.300000000004=D(3FD333333 333333 4)

最接近的

  • 0.100000000000000001+0.20000000000000001为0.30000000000000004

因此,您将比较0.299999999999和0.30000000000000004。这能让你更深入地了解正在发生的事情吗?

至于使用十进制代替二进制表示,这也不起作用。以三分之一为例:

  • 1/3=0.333333333333333333333333333333333333……

即使使用十进制数字也无法精确表示。任何计算都应该考虑表示错误。


误差不是舍入误差,只是有些值不能用IEEE754标准精确表示。请参阅jon skeet关于.net中二进制浮点数的文章,以获取进一步的阅读。

为了处理像您的示例(base-10)这样的数字,您应该使用c中的decimal数据类型,因为它可以准确地表示这些数字,所以您可以得到预期的值。

一种典型的方法是定义一些epsilon值,并检查结果是否在targetValue+-epsilon范围内:

1
2
3
4
5
6
double const epsilon = 0.000001; // or whatever

if(valueA >= valueB - epsilon && valueA <= valueB + epsilon)
{
    // treat as valueA = valueB
}


既然你知道了这个问题,解决方法就是在你计算浮点数的时候记住它。

您的示例不完全是您将在实际程序中使用的。但是,如果真的需要,有一些方法可以评估这些东西,一个例子(用c)可以是……

1
if((0.1f + 0.2f).ToString("0.0") =="0.3")

这是真的,可能还有很多其他的方法。关键是,如果你曾经遇到过这种情况,那么记住潜在的问题。正是这种经验造就了更好的开发人员/程序员