考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
当前回答
存储在计算机中的浮点数由两部分组成,一部分是整数,另一部分是基数乘以整数部分的指数。
如果计算机在基数为10的情况下工作,则0.1将是1 x 10⁻¹,0.2将是2 x 10⁻¹,0.3将是3 x 10⁻¹. 整数运算简单而准确,所以加上0.1+0.2显然会得到0.3。
计算机通常不以10为基数工作,而是以2为基数工作。对于某些值,仍然可以得到精确的结果,例如0.5是1 x 2⁻¹和0.25是1 x 2⁻²,将它们相加,结果为3 x 2⁻²或0.75。确切地
问题是数字可以精确地以10为基数表示,但不能以2为基数。这些数字需要四舍五入到最接近的相等值。假设非常常见的IEEE 64位浮点格式,最接近0.1的数字是3602879701896397 x 2⁻⁵⁵, 最接近0.2的数字是7205759403792794 x 2⁻⁵⁵; 将它们相加,得到10808639105689191 x 2⁻⁵⁵, 或精确的十进制值0.30000000000000000444089209850062616169452667236328125。浮点数通常四舍五入以显示。
其他回答
在硬件级别,浮点数表示为二进制数的分数(以2为基数)。例如,小数:
0.125
具有1/10+2/100+5/1000的值,并且以相同的方式,具有二进制分数:
0.001
值为0/2+0/4+1/8。这两个分数具有相同的值,唯一的区别是第一个是小数,第二个是二进制分数。
不幸的是,大多数十进制分数不能用二进制分数表示。因此,通常情况下,您给出的浮点数仅近似于存储在机器中的二进制分数。
这个问题在基础10中更容易解决。以分数1/3为例。您可以将其近似为小数:
0.3
或更好,
0.33
或更好,
0.333
无论你写了多少个小数点,结果永远不会精确到1/3,但这是一个总是更接近的估计。
同样,无论使用多少个以2为基数的小数位数,小数值0.1都不能精确地表示为二进制小数。在基数2中,1/10是以下周期数:
0.0001100110011001100110011001100110011001100110011 ...
停止在任何有限数量的比特,你会得到一个近似值。
对于Python,在典型的机器上,53位用于浮点的精度,因此输入小数0.1时存储的值是二进制小数。
0.00011001100110011001100110011001100110011001100110011010
其接近但不完全等于1/10。
很容易忘记存储的值是原始小数的近似值,因为在解释器中显示浮点的方式。Python只显示二进制存储值的十进制近似值。如果Python要输出存储为0.1的二进制近似值的真正十进制值,它将输出:
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
这比大多数人预期的小数位数要多得多,因此Python显示舍入值以提高可读性:
>>> 0.1
0.1
重要的是要理解,在现实中这是一种错觉:存储的值不完全是1/10,只是在显示器上存储的值被舍入。当您使用这些值执行算术运算时,这一点就会变得明显:
>>> 0.1 + 0.2
0.30000000000000004
这种行为是机器浮点表示的本质所固有的:它不是Python中的错误,也不是代码中的错误。你可以在所有其他语言中观察到相同类型的行为使用硬件支持计算浮点数(尽管有些语言默认情况下不使差异可见或在所有显示模式下不可见)。
另一个令人惊讶的地方就在这一点上。例如,如果尝试将值2.675舍入到两位小数,则会得到
>>> round (2.675, 2)
2.67
round()原语的文档表明它舍入到离零最近的值。由于小数正好在2.67和2.68之间的一半,因此应该可以得到2.68(二进制近似值)。然而,情况并非如此,因为当小数2.675转换为浮点时,它由精确值为:
2.67499999999999982236431605997495353221893310546875
由于近似值比2.68略接近2.67,因此舍入值降低。
如果您处于小数向下舍入的情况,那么应该使用十进制模块。顺便说一下,十进制模块还提供了一种方便的方式来“查看”为任何浮点存储的确切值。
>>> from decimal import Decimal
>>> Decimal (2.675)
>>> Decimal ('2.67499999999999982236431605997495353221893310546875')
0.1不是精确存储在1/10中这一事实的另一个结果是十个值的总和0.1也不等于1.0:
>>> sum = 0.0
>>> for i in range (10):
... sum + = 0.1
...>>> sum
0.9999999999999999
二进制浮点数的算术有很多这样的惊喜。“0.1”的问题将在下文“表示错误”一节中详细解释。有关此类惊喜的更完整列表,请参阅浮点运算的危险。
确实没有简单的答案,但是不要对浮动虚拟数字过分怀疑!在Python中,浮点数操作中的错误是由底层硬件造成的,在大多数机器上,每次操作的错误率不超过1/2*53。这对于大多数任务来说都是非常必要的,但您应该记住,这些操作不是十进制操作,并且对浮点数字的每一次操作都可能会出现新的错误。
尽管存在病态的情况,但对于大多数常见的用例,您只需在显示器上舍入到所需的小数位数,就可以在最后得到预期的结果。有关如何显示浮点数的详细控制,请参阅字符串格式语法以了解str.format()方法的格式规范。
答案的这一部分详细解释了“0.1”的示例,并展示了如何自己对此类案例进行精确分析。我们假设您熟悉浮点数的二进制表示。术语表示错误意味着大多数小数不能用二进制精确表示。这就是为什么Python(或Perl、C、C++、Java、Fortran等)通常不会以十进制显示精确结果的主要原因:
>>> 0.1 + 0.2
0.30000000000000004
为什么?1/10和2/10不能用二进制分数精确表示。然而,今天(2010年7月)所有的机器都遵循IEEE-754标准来计算浮点数。大多数平台使用“IEEE-754双精度”来表示Python浮点。双精度IEEE-754使用53位精度,因此在读取时,计算机尝试将0.1转换为J/2*N形式的最接近分数,J正好是53位的整数。重写:
1/10 ~ = J / (2 ** N)
in :
J ~ = 2 ** N / 10
记住J正好是53位(所以>=2**52但<2**53),N的最佳可能值是56:
>>> 2 ** 52
4503599627370496
>>> 2 ** 53
9007199254740992
>>> 2 ** 56/10
7205759403792793
因此,56是N的唯一可能值,正好为J保留53位。因此,J的最佳可能值是这个商,四舍五入:
>>> q, r = divmod (2 ** 56, 10)
>>> r
6
由于进位大于10的一半,通过四舍五入获得最佳近似值:
>>> q + 1
7205759403792794
因此,“IEEE-754双精度”中1/10的最佳近似值为2**56以上,即:
7205759403792794/72057594037927936
注意,由于四舍五入是向上进行的,结果实际上略大于1/10;如果我们没有四舍五入,这个商会略小于1/10。但无论如何都不是1/10!
因此,计算机从未“看到”1/10:它看到的是上面给出的精确分数,这是使用“IEEE-754”中的双精度浮点数的最佳近似值:
>>>. 1 * 2 ** 56
7205759403792794.0
如果我们将这个分数乘以10**30,我们可以观察到这些值它的30位小数具有很强的权重。
>>> 7205759403792794 * 10 ** 30 // 2 ** 56
100000000000000005551115123125L
这意味着存储在计算机中的精确值近似等于十进制值0.100000000000000005551115123125。在Python 2.7和Python 3.1之前的版本中,Python舍入这些值到17位有效小数,显示“0.10000000000000001”。在当前版本的Python中,显示的值是分数尽可能短的值,当转换回二进制时,给出的表示形式完全相同,只需显示“0.1”。
一些统计数据与这个著名的双精度问题有关。
当使用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%)。
我的解决方法:
function add(a, b, precision) {
var x = Math.pow(10, precision || 2);
return (Math.round(a * x) + Math.round(b * x)) / x;
}
精度是指在加法过程中要保留小数点后的位数。
你试过胶带解决方案了吗?
尝试确定错误发生的时间,并用简短的if语句修复它们,这并不漂亮,但对于某些问题,这是唯一的解决方案,这就是其中之一。
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
我在c#的一个科学模拟项目中也遇到过同样的问题,我可以告诉你,如果你忽视蝴蝶效应,它会变成一条大胖龙,咬你一口**
鉴于没有人提到这一点。。。
一些高级语言(如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类。两者都将有理数表示为(分子、分母)对,它们可能给出比十进制浮点运算更精确的结果。
这两种解决方案都不是完美的(特别是如果我们考虑性能,或者如果我们需要非常高的精度),但它们仍然解决了二进制浮点运算的大量问题。