考虑以下代码:
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
为什么会出现这些错误?
当前回答
在硬件级别,浮点数表示为二进制数的分数(以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”。
其他回答
简而言之,这是因为:
浮点数不能以二进制精确表示所有小数
因此,就像10/3不精确地存在于基数10中(它将是3.33……重复出现)一样,1/10也不存在于二进制中。
那又怎么样?如何处理?有什么解决办法吗?
为了提供最佳解决方案,我可以说我发现了以下方法:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
让我解释一下为什么这是最好的解决方案。正如上面提到的其他答案一样,使用现成的Javascript toFixed()函数来解决问题是一个好主意。但很可能你会遇到一些问题。
假设你将两个浮点数相加,如0.2和0.7,这里是:0.2+0.7=0.8999999999999999。
您的预期结果是0.9,这意味着您需要一个精度为1位数的结果。因此,您应该使用(0.2+0.7).tfixed(1)但是不能只给toFixed()一个特定的参数,因为它取决于给定的数字,例如
0.22 + 0.7 = 0.9199999999999999
在本例中,您需要2位精度,因此它应该为Fixed(2),那么,适合每个给定浮点数的参数应该是什么?
你可以说在每种情况下都是10:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
该死你打算怎么处理那些9后不需要的零?现在是将其转换为浮动的时候了,以实现您的愿望:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
既然找到了解决方案,那么最好将其作为如下函数提供:
function floatify(number){
return parseFloat((number).toFixed(10));
}
让我们自己试试吧:函数floatify(数字){return parseFloat((number).toFixed(10));}函数addUp(){var number1=+$(“#number1”).val();var number2=+$(“#number2”).val();var expectedResult=number1+number2;var expectedResult=浮动(number1+number2);$(“#意外结果”).text(意外结果);$(“#expectedResult”).text(expectedResult);}addUp();输入{宽度:50px;}#预期结果{颜色:绿色;}#未预期结果{颜色:红色;}<script src=“https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js“></script><input id=“number1”value=“0.2”onclick=“addUp()”onkeyup=“addUp()”/>+<input id=“number2”value=“0.7”onclick=“addUp()”onkeyup=“addUp()”/>=<p>预期结果:<span id=“expectedResult”></span></p><p>意外结果:<span id=“expectedResult”></span></p>
您可以这样使用:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
正如W3SCHOOLS所建议的,还有另一种解决方案,您可以通过乘法和除法来解决上述问题:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
请记住,(0.2+0.1)*10/10根本不起作用,尽管看起来是一样的!我更喜欢第一种解决方案,因为我可以将其作为一个函数应用,将输入浮点转换为精确的输出浮点。
仅供参考,乘法也存在同样的问题,例如0.09*10返回0.8999999999999999。应用flotify函数作为解决方法:flotify(0.09*10)返回0.9
这里的大多数答案都用非常枯燥的技术术语来解决这个问题。我想用正常人能够理解的方式来解决这个问题。
想象一下,你正试图把披萨切成薄片。你有一个机器人披萨切割机,可以将披萨切成两半。它可以将整个披萨减半,也可以将现有的披萨减半,但无论如何,减半总是准确的。
那台披萨切割机动作非常精细,如果你从一整块披萨开始,然后将其减半,然后继续每次将最小的披萨片减半,你可以在披萨片太小甚至无法实现高精度功能之前,将其减半53次。此时,您不能再将非常薄的切片减半,但必须按原样包含或排除它。
现在,你如何将所有的切片以这样一种方式分割,使其达到披萨的十分之一(0.1)或五分之一(0.2)?真的想一想,试着解决它。如果你手边有一个神话般的精密披萨切割机,你甚至可以尝试使用真正的披萨
当然,大多数有经验的程序员都知道真正的答案,那就是,无论你切得多细,都无法用这些切片拼凑出十分之一或五分之一的披萨。你可以做一个非常好的近似值,如果你把0.1的近似值和0.2的近似值相加,你会得到非常好的0.3的近似值。
对于双精度数字(允许您将披萨减半53次的精度),小于或大于0.1的数字分别为0.09999999999999999167332731531132594682276248931884765625和0.1000000000000000055511151231257827021181583404541015625。后者比前者更接近0.1,因此,如果输入值为0.1,数字解析器将倾向于后者。
(这两个数字之间的区别是“最小切片”,我们必须决定是否包含,这会引入向上的偏差,或者排除,这会带来向下的偏差。最小切片的技术术语是ulp。)
在0.2的情况下,数字都是相同的,只是放大了2倍。同样,我们赞成略高于0.2的值。
注意,在这两种情况下,0.1和0.2的近似值都有轻微的向上偏差。如果我们加上足够多的这些偏差,它们会将数字推离我们想要的越来越远,事实上,在0.1+0.2的情况下,偏差足够高,从而导致的数字不再是最接近0.3的数字。
特别是,0.1+0.2实际上是0.1000000000000000055511151231257827021181583404541015625+0.0200000000000000011102230246251565404236316680908203125=0.30000000000000000444089209850062616169452667236328125,而最接近0.3的数字实际上是0.29999999999988897769753748434595763683319091796875。
另外,一些编程语言还提供了披萨切割机,可以将披萨切成十分之一。虽然这种披萨切刀并不常见,但如果你有机会切到一个,那么你应该在切到十分之一或五分之一的披萨片非常重要的时候使用它。
(最初发布在Quora上。)
浮点数的陷阱是它们看起来像十进制,但它们是二进制的。
2的唯一素因子是2,而10的素因子为2和5。这样做的结果是,每一个可以完全写成二进制分数的数字也可以完全写成十进制分数,但只有一部分可以写成十进制分数的数字可以写成二进制分数。
浮点数本质上是一个有效位数有限的二进制分数。如果你超过这些有效数字,那么结果将被四舍五入。
当您在代码中键入文字或调用函数将浮点数解析为字符串时,它需要一个十进制数,并将该十进制数的二进制近似值存储在变量中。
当您打印浮点数或调用函数将浮点数转换为字符串时,它将打印浮点数的十进制近似值。可以将二进制数字精确地转换为十进制,但在转换为字符串*时,我所知道的任何语言都不会默认这样做。一些语言使用固定数量的有效数字,其他语言使用最短的字符串,该字符串将“往返”返回到相同的浮点值。
*Python在将浮点数转换为“decimal.decimal”时确实会进行精确的转换。这是我所知道的获得浮点数的精确十进制等效值的最简单方法。
浮点舍入错误。由于缺少5的素因子,0.1在基-2中不能像在基-10中那样精确地表示。正如1/3以十进制表示需要无限位数,但以3为基数表示为“0.1”,0.1以2为基数表示,而以10为基数不表示。计算机没有无限的内存。
二进制浮点数学是这样的。在大多数编程语言中,它基于IEEE 754标准。问题的关键在于,数字以这种格式表示为整数乘以2的幂;分母不是2的幂的有理数(如0.1,即1/10)无法精确表示。
对于标准binary64格式的0.1,表示形式可以完全写为
0.1000000000000000055511151231257827021181583404541015625(十进制),或0x1.999999999999ap-4,采用C99六进制浮点数表示法。
相比之下,有理数0.1(1/10)可以完全写成
0.1(十进制),或0x1.999999999999999…p-4,类似于C99十六进制浮点数,其中。。。表示9的无限序列。
程序中的常数0.2和0.3也将近似于其真实值。恰好最接近0.2的两倍大于有理数0.2,但最接近0.3的两倍小于有理数0.3。0.1和0.2的和最终大于有理数0.3,因此与代码中的常数不一致。
浮点运算问题的一个相当全面的处理是每个计算机科学家都应该知道的浮点运算。有关更容易理解的解释,请参阅floatingpoint-gui.de。
边注:所有位置(以N为基数)数字系统都有精度问题
普通的十进制(以10为基数)数字也有同样的问题,这就是为什么像1/3这样的数字最终会变成0.33333333。。。
您刚刚偶然发现了一个数字(3/10),它很容易用十进制表示,但不适合二进制。它也是双向的(在某种程度上):1/16在十进制中是一个丑陋的数字(0.0625),但在二进制中,它看起来和十进制中的第10000个一样整洁(0.0001)**-如果我们在日常生活中习惯使用基数为2的数字系统,你甚至会看着这个数字,本能地理解你可以通过将某个数字减半,一次又一次地减半来达到这个目的。
当然,这并不是浮点数在内存中的存储方式(它们使用了一种科学的表示法)。然而,它确实说明了一点,二进制浮点精度错误往往会出现,因为我们通常感兴趣的“真实世界”数字往往是十的幂,但这只是因为我们每天使用十进制数字系统。这也是为什么我们会说71%而不是“每7取5”(71%是一个近似值,因为5/7不能用任何小数精确表示)。
所以不:二进制浮点数并没有被破坏,它们只是碰巧和其他N进制一样不完美:)
边注:在编程中使用浮点
实际上,这种精度问题意味着在显示浮点数之前,需要使用舍入函数将浮点数舍入到您感兴趣的小数位数。
您还需要用允许一定公差的比较来替换相等测试,这意味着:
如果(x==y){…}则不执行
相反,如果(abs(x-y)<myToleranceValue){…},则执行此操作。
其中abs是绝对值。需要为您的特定应用程序选择myToleranceValue,这与您准备允许多少“摆动空间”以及您将要比较的最大值(由于精度损失问题)有很大关系。当心您选择的语言中的“epsilon”样式常量。这些值可以用作公差值,但它们的有效性取决于您使用的数字的大小,因为使用大数字的计算可能会超过epsilon阈值。