为什么4 * 0.1的浮点值在Python 3中看起来不错,但是3 * 0.1却没有?

我知道大多数小数没有一个精确的浮点表示( 浮点math是否被破坏? )。

但是我不明白为什么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') 

简单的答案是因为3*0.1 != 0.3由于量化(舍入)误差(而4*0.1 == 0.4因为乘以2的幂通常是“精确的”操作)。

您可以在Python中使用.hex方法来查看数字的内部表示(基本上是精确的二进制浮点值,而不是基于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的浮动repr被devise为可循环的 ,也就是说,所显示的值应该可以完全转换为原始值。 因此,不能以完全相同的方式显示0.30.1*3 ,或者两个不同的数字在往返之后结束。 因此,Python 3的repr引擎select显示一个稍微明显的错误。

repr (和Python 3中的str )会根据需要输出尽可能多的数字,以使值清晰。 在这种情况下,乘法3*0.1的结果不是0.3(0x1.3333333333333p-2hex)中的最接近的值,实际上它是一个LSB​​(0x1.3333333333334p-2),所以需要更多的数字来区分它0.3。

另一方面,乘法4*0.1的值最接近0.4(hex中的0x1.999999999999ap-2),因此不需要任何附加数字。

你可以很容易地validation这一点:

 >>> 3*0.1 == 0.3 False >>> 4*0.1 == 0.4 True 

上面我使用了hex符号,因为它很好,紧凑,并显示了两个值之间的位差。 你可以使用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') 

这是其他答案的简单结论。

如果你在Python的命令行上检查一个float,或者打印它,它会通过函数repr来创build它的string表示。

从3.2版本开始,Python的strrepr使用了一个复杂的舍入scheme,如果可能的话,它更喜欢漂亮的小数,但在必要时使用更多的数字来保证浮点数和它们的string表示之间的双射(one-to-one)映射。

这个scheme保证了repr(float(s))值对于简单的小数很好看,即使它们不能精确地表示为浮点数(例如,当s = "0.1")

同时它保证float(repr(x)) == x对于每个浮点x

没有真正具体到Python的实现,但应适用于任何浮点数到十进制string函数。

一个浮点数本质上是一个二进制数,但是在科学记数法中有一个固定的有效数字极限。

具有不与基数共享的素数因子的任何数字的倒数将始终导致循环的点表示forms。 例如,1/7有一个素数因子7,它不与10共享,因此有一个重复的十进制表示,对质量因子2和5而言1/10也是如此,后者不与2共享; 这意味着0.1点不能完全由点点之后的有限数量的位表示。

由于0.1没有精确的表示,因此将近似值转换为小数点string的函数通常会尝试近似某些值,以免得到不直观的结果,如0.1000000000004121。

由于浮点数是以科学计数法表示的,任何基数乘法的乘法只影响数的指数部分。 例如,对于十进制表示法,1.231e + 2 * 100 = 1.231e + 4,同样,二进制表示法中的1.00101010e11 * 100 = 1.00101010e101。 如果我乘以基数的非力量,有效数字也将受到影响。 例如1.2e1 * 3 = 3.6e1

根据所使用的algorithm,它可能会尝试仅基于有效数字来猜测普通小数。 因为它们的浮点数基本上是(8/5) (2 ^ -4)和(8/5) (2 ^ -6)的截断,所以0.1和0.4在二进制中有相同的有效数字。 如果algorithm将8/5 sigfig模式识别为十进制数1.6,则它将在0.1,0.2,0.4,0.8等等上工作。对于其他组合,也可能具有神奇的sigfig模式,例如浮点数3除以浮点数10以及统计上可能由10除法形成的其他魔法图案。

在3 * 0.1的情况下,最后的几个有效数字可能不同于将浮点数3除以浮点数10,导致algorithm不能根据其精度损失容差来识别0.3常数的幻数。

编辑: https : //docs.python.org/3.1/tutorial/floatingpoint.html

有趣的是,有许多不同的十进制数字共享相同的最近似的二进制分数。 例如,数字0.1和0.10000000000000001和0.1000000000000000055511151231257827021181583404541015625都近似为3602879701896397/2 ** 55.由于所有这些十进制值共享相同的近似值,它们中的任何一个都可以显示,同时仍然保留不变的eval(repr(x) )== x。

如果浮点数x(0.3)与浮点数y(0.1 * 3)不完全相等,那么精度损失就不存在容差,则repr(x)与repr(y)不完全相等。