问题:为什么4 * 0.1的浮点值在Python 3中看起来不错,但3 * 0.1却不这样?
我知道大多数小数都没有确切的浮点表示形式(浮点数学运算符是否损坏?)。
但是,当两个值实际上都具有丑陋的十进制表示形式时,我看不出为什么4*0.1
将其很好地打印为0.4
,但3*0.1
不是这样:
>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
回答 0
简单的答案是因为3*0.1 != 0.3
归因于量化(四舍五入)误差(而4*0.1 == 0.4
乘以2的幂通常是“精确”运算)。
您可以.hex
在Python中使用该方法查看数字的内部表示形式(基本上是确切的二进制浮点值,而不是以10为底的近似值)。这可以帮助解释幕后情况。
>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'
0.1是0x1.999999999999a乘以2 ^ -4。末尾的“ a”表示数字10-换句话说,二进制浮点数中的0.1 略大于 “精确”值0.1(因为最终的0x0.99舍入为0x0.a)。当您将其乘以4(2的幂)时,指数会上移(从2 ^ -4到2 ^ -2),但数字不变,所以4*0.1 == 0.4
。
但是,当乘以3时,0x0.99与0x0.a0(0x0.07)之间的微小差异会放大为0x0.15错误,在最后一个位置显示为一位错误。这将导致0.1 * 3 略大于 0.3的舍入值。
Python 3的float repr
被设计为可双向访问的,也就是说,显示的值应完全可转换为原始值。因此,它无法显示0.3
和0.1*3
完全相同的方式,或两个不同的数字最终会往返后相同。因此,Python 3的repr
引擎选择显示一个略有明显错误的引擎。
回答 1
repr
(str
在Python 3中)将根据需要输出尽可能多的数字,以使该值明确。在这种情况下,相乘的结果3*0.1
不是最接近0.3的值(十六进制为0x1.3333333333333p-2),实际上是高了一个LSB(0x1.3333333333334p-2),因此它需要更多的数字才能与0.3区分。
另一方面,乘法4*0.1
的确获得了最接近0.4的值(十六进制为0x1.999999999999ap-2),因此不需要任何其他数字。
您可以很容易地验证这一点:
>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True
我在上面使用了十六进制表示法,因为它既美观又紧凑,并且显示了两个值之间的位差。您可以使用eg自己执行此操作(3*0.1).hex()
。如果您希望以全部十进制的形式查看它们,请执行以下操作:
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')
回答 2
这是其他答案的简化结论。
如果您在Python的命令行上检查浮点数或将其打印,它将通过
repr
创建其字符串表示形式的函数。从3.2版开始,Python
str
和repr
使用复杂的舍入机制,其更喜欢好看的小数,如果有可能,但使用更多的数字在需要保证双射(一个一对一)映射花车和它们的字符串表示之间。这种方案保证
repr(float(s))
即使简单的小数点不能精确地表示为浮点数(例如when),其值对于简单的小数点也看起来不错s = "0.1")
。同时,它保证
float(repr(x)) == x
每个浮动都成立x
回答 3
并不是真的特定于Python的实现,而是应该适用于任何浮点数到十进制字符串的函数。
浮点数本质上是一个二进制数,但以科学计数法表示,有效数字的固定限制。
具有不与底数共享的质数因子的任何数字的逆将始终导致重复的点表示。例如1/7的素数为7,与10不共享,因此具有重复的十进制表示形式,素数为2和5的1/10也是如此,后者不与2共享; 这意味着0.1不能由点后的有限位数精确表示。
由于0.1没有精确的表示形式,因此将近似值转换为小数点字符串的函数通常将尝试近似某些值,以使它们不会像0.1000000000004121那样获得不直观的结果。
由于浮点数是科学计数法,因此任何乘以基数的幂只会影响数的指数部分。例如,十进制表示法为1.231e + 2 * 100 = 1.231e + 4,同样,二进制表示法为1.00101010e11 * 100 = 1.00101010e101。如果我乘以非底数的幂,则有效数字也会受到影响。例如1.2e1 * 3 = 3.6e1
根据所使用的算法,它可能会尝试仅根据有效数字来猜测常见的小数。0.1和0.4都具有相同的二进制有效数字,因为它们的浮点数本质上分别是(8/5)(2 ^ -4)和(8/5)(2 ^ -6)的截断。如果该算法将8/5 sigfig模式标识为十进制1.6,则它将适用于0.1、0.2、0.4、0.8等。对于其他组合(例如,浮点数3除以浮点数10),它也可能具有魔术的sigfig模式。以及其他统计上可能由10除以形成的魔术图案。
在3 * 0.1的情况下,最后几个有效数字可能与将浮点数3除以浮点数10有所不同,从而导致算法无法根据其对精度损失的容忍度来识别0.3常数的幻数。
编辑:https: //docs.python.org/3.1/tutorial/floatingpoint.html
有趣的是,有许多不同的十进制数字共享相同的最接近的近似二进制分数。例如,数字0.1和0.10000000000000001和0.1000000000000000055511151231257827021181583404541015625都由3602879701896397/2 ** 55近似。由于所有这些十进制值都具有相同的近似值,因此可以显示其中任何一个,同时仍保留不变的eval(repr(x) )== x。
对于精度损失没有容忍度,如果float x(0.3)不完全等于float y(0.1 * 3),则repr(x)不完全等于repr(y)。