考虑以下代码:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

为什么会出现这些错误?


当前回答

一些统计数据与这个著名的双精度问题有关。

当使用0.1(从0.1到100)的步长将所有值(a+b)相加时,精度误差的概率约为15%。请注意,该错误可能会导致稍大或稍小的值。以下是一些示例:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

当使用0.1(从100到0.1)的步长减去所有值(a-b,其中a>b)时,我们有大约34%的精度误差。以下是一些示例:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

*15%和34%确实是巨大的,所以当精度非常重要时,请始终使用BigDecimal。使用2个十进制数字(步骤0.01),情况会进一步恶化(18%和36%)。

其他回答

想象一下,以10为基数,例如8位数的精度工作。您检查是否

1/3 + 2 / 3 == 1

并了解到这返回错误。为什么?好吧,作为真实的数字

1/3=0.333….和2/3=0.666。。。。

在小数点后八位截断,我们得到

0.33333333 + 0.66666666 = 0.99999999

当然,这与1.00000000正好相差0.00000001。


具有固定位数的二进制数的情况完全类似。作为实数,我们有

1/10=0.0001100110011001100…(底座2)

and

1/5=0.00111001100110011001…(底座2)

如果我们把这些截成七位

0.0001100 + 0.0011001 = 0.0100101

而另一方面,

3/10=0.010011001100110011…(基数2)

被截断为七位的值为0.0100110,两者相差0.0000001。


确切的情况稍显微妙,因为这些数字通常以科学符号存储。因此,例如,我们可以将其存储为1.10011*2^-4,而不是将1/10存储为0.0001100,这取决于我们为指数和尾数分配了多少位。这会影响计算的精度位数。

结果是,由于这些舍入错误,您根本不想在浮点数上使用==。相反,您可以检查它们的差值的绝对值是否小于某个固定的小数字。

这些奇怪的数字之所以出现,是因为计算机使用二进制(以2为基数)数字系统进行计算,而我们使用十进制(以10为基数)。

大多数分数不能用二进制或十进制或两者精确表示。结果-四舍五入(但精确)的数字结果。

鉴于没有人提到这一点。。。

一些高级语言(如Python和Java)提供了克服二进制浮点限制的工具。例如:

Python的十进制模块和Java的BigDecimal类,它们在内部使用十进制表示法(与二进制表示法相反)表示数字。两者都有有限的精度,因此它们仍然容易出错,但它们解决了二进制浮点运算中最常见的问题。小数在处理金钱时很好:10美分加20美分总是正好是30美分:>>> 0.1 + 0.2 == 0.3错误>>>十进制('0.1')+十进制('0.2')==十进制('0.3')真的Python的十进制模块基于IEEE标准854-1987。Python的分数模块和Apache Common的BigFraction类。两者都将有理数表示为(分子、分母)对,它们可能给出比十进制浮点运算更精确的结果。

这两种解决方案都不是完美的(特别是如果我们考虑性能,或者如果我们需要非常高的精度),但它们仍然解决了二进制浮点运算的大量问题。

我的解决方法:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

精度是指在加法过程中要保留小数点后的位数。

浮点数的陷阱是它们看起来像十进制,但它们是二进制的。

2的唯一素因子是2,而10的素因子为2和5。这样做的结果是,每一个可以完全写成二进制分数的数字也可以完全写成十进制分数,但只有一部分可以写成十进制分数的数字可以写成二进制分数。

浮点数本质上是一个有效位数有限的二进制分数。如果你超过这些有效数字,那么结果将被四舍五入。

当您在代码中键入文字或调用函数将浮点数解析为字符串时,它需要一个十进制数,并将该十进制数的二进制近似值存储在变量中。

当您打印浮点数或调用函数将浮点数转换为字符串时,它将打印浮点数的十进制近似值。可以将二进制数字精确地转换为十进制,但在转换为字符串*时,我所知道的任何语言都不会默认这样做。一些语言使用固定数量的有效数字,其他语言使用最短的字符串,该字符串将“往返”返回到相同的浮点值。

*Python在将浮点数转换为“decimal.decimal”时确实会进行精确的转换。这是我所知道的获得浮点数的精确十进制等效值的最简单方法。