【问题标题】:Why `0.4/2` equals to `0.2` meanwhile `0.6/3` equals to `0.19999999999999998` in python? [duplicate]为什么`0.4/2`等于`0.2`而`0.6/3`在python中等于`0.19999999999999998`? [复制]
【发布时间】:2015-05-10 05:03:39
【问题描述】:

我知道这些是浮点除法。但是为什么这两个公式的行为不同呢?

我又做了一些调查,结果让我更加困惑:

>>>0.9/3
0.3

>>>1.2/3
0.39999999999999997

>>>1.5/3
0.5

这里的逻辑是什么来决定结果是打印一个小数位还是更多位?

PS:上面的实验我用python3.4做的。

【问题讨论】:

标签: python floating-point floating-accuracy ieee-754


【解决方案1】:

根据IEEE 754,浮点数被实现为binary64(几乎在所有编程语言中)。

此标准为“有效数/小数”提供 52 位(大约 16 位十进制数字精度),11 位为指数,1 位为符号(正或负):

特别是像0.4这样的数字不能表示为

(1 + f) * 2**(exponent)

对于一些以 2 为底的分数和一个可以用 11 位(-1022 到 1023)表示的指数。

例如以十六进制查看0.4

>>> (0.4).hex()
'0x1.999999999999ap-2'

我们看到我们的一组数字中的最佳近似值是

+ 2**(-2) * (1 + 0x999999999999a/ float(2**52))

试图以 2 为基数表示,我们有

2**(-2) * (1 + 0.6)

0.6 = 9/15 = 1001_2/1111_2 以 2 为基数编写有一个重复的四位二进制数字字符串

0.1001100011000110001...

所以永远不能用有限数量的二进制数字来表示。


更深入一点

所以我们可以“解包”0.4

>>> import struct
>>> # 'd' for double, '>' for Big-endian (left-to-right bits)
>>> float_bytes = struct.pack('>d', 0.4)

8 字节(1 字节为 8 位)

>>> float_bytes
'?\xd9\x99\x99\x99\x99\x99\x9a'

或作为 16 个十六进制数字(1 个十六进制数字是 4 位,因为 2**4 == 16

>>> ''.join(['%2x' % (ord(digit),) for digit in float_bytes])
'3fd999999999999a'

或作为荣耀的所有 64 位

>>> float_bits = ''.join(['%08d' % (int(bin(ord(digit))[2:]),)
...                       for digit in float_bytes])
>>> float_bits
'0011111111011001100110011001100110011001100110011001100110011010'

从那里开始,第一位是符号位:

>>> sign = (-1)**int(float_bits[0], 2)
>>> sign
1

接下来的 11 位是指数(但移动了 1023,一个约定 binary64):

>>> exponent = int(float_bits[1:1 + 11], 2) - 1023
>>> exponent
-2

最后52位是小数部分

>>> fraction = int(float_bits[1 + 11:1 + 11 + 52], 2)
>>> fraction
2702159776422298
>>> hex(fraction)
'0x999999999999a'

把它们放在一起

>>> sign * 2**(exponent) * (1 + fraction / float(2**52))
0.4

【讨论】:

  • 你的意思是 binary64
  • 天啊!感谢您指出,已编辑。
【解决方案2】:

因为浮点结果的精确值略有不同。

>>> '%.56f' % 0.4
'0.40000000000000002220446049250313080847263336181640625000'
>>> '%.56f' % (0.4/2)
'0.20000000000000001110223024625156540423631668090820312500'
>>> '%.56f' % 0.6
'0.59999999999999997779553950749686919152736663818359375000'
>>> '%.56f' % (0.6/3)
'0.19999999999999998334665463062265189364552497863769531250'
>>> '%.56f' % 0.2
'0.20000000000000001110223024625156540423631668090820312500'
>>> (0.2 - 0.6/3) == 2.0**-55
True

如您所见,打印为“0.2”的结果确实稍微接近于 0.2。我在最后添加了一点,以向您展示这两个数字之间差异的确切值是多少。 (如果您好奇,以上表示是确切的值 - 在此之外添加任意数量的数字只会添加更多的零)。

【讨论】:

    【解决方案3】:

    查看floating point numbers in python 上的文档。

    具体来说:

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

    从历史上看,Python 提示符和内置 repr() 函数会选择具有 17 个有效数字的那个,即 0.10000000000000001。从 Python 3.1 开始,Python(在大多数系统上)现在可以选择其中最短的并简单地显示 0.1。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-17
      • 2014-07-04
      • 2011-04-24
      • 2015-04-08
      • 2020-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多