首页 > 解决方案 > 解释区间 [0, 1] 中明显关系的舍入方向的惊人奇偶性

问题描述

考虑在和0.xx5之间的浮点数集合:0.01.0[0.005, 0.015, 0.025, 0.035, ..., 0.985, 0.995]

我可以在 Python 中轻松列出所有 100 个这样的数字:

>>> values = [n/1000 for n in range(5, 1000, 10)]

让我们看看前几个和最后几个值,以检查我们没有犯任何错误:

>>> values[:8]
[0.005, 0.015, 0.025, 0.035, 0.045, 0.055, 0.065, 0.075]
>>> values[-8:]
[0.925, 0.935, 0.945, 0.955, 0.965, 0.975, 0.985, 0.995]

现在我想将这些数字中的每一个四舍五入到小数点后两位。一些数字将被四舍五入;有些会被四舍五入。我有兴趣准确计算有多少舍入。我也可以在 Python 中轻松计算:

>>> sum(round(value, 2) > value for value in values)
50

所以事实证明,这 100 个数字中恰好有一半被四舍五入。

如果您不知道 Python 在后台使用二进制浮点,那么这个结果就不足为奇了。毕竟,Python 的文档清楚地表明该round函数使用round-ties-to-even(又名Banker 的舍入)作为其舍入模式,因此您希望这些值交替向上和向下舍入。

但是 Python确实在底层使用了二进制浮点,这意味着除了少数例外(即0.1250.375和) 0.6250.875这些值不是精确的关系,而只是这些关系的非常好的二进制近似值。毫不奇怪,对四舍五入结果的仔细检查表明,这些值不会交替上下四舍五入。相反,每个值根据二进制近似值恰好落在十进制值的哪一侧而向上或向下舍入。因此,没有先验理由期望恰好一半的值向上舍入,而恰好一半的值向下舍入。这让我们得到的结果正好是 50 有点令人惊讶。

但也许我们只是走运了?毕竟,如果你投掷一枚公平的硬币 100 次,恰好得到 50 个正面的结果并不是什么不寻常的结果:它发生的概率约为 8%。但事实证明,这种模式持续存在更多的小数位数。这是舍入到小数点后 6 位时的类似示例:

>>> values = [n/10**7 for n in range(5, 10**7, 10)]
>>> sum(round(value, 6) > value for value in values)
500000

在这里,它再次将明显的关系四舍五入到小数点后 8 位:

>>> values = [n/10**9 for n in range(5, 10**9, 10)]
>>> sum(round(value, 8) > value for value in values)
50000000

所以问题是:为什么恰好有一半的案例会凑齐?或者换一种说法,为什么在这些十进制关系的所有二进制近似值中,大于真实值的近似值的数量与小于真实值的近似值的数量完全匹配?(可以很容易地证明,对于精确的情况,我们有相同的向上轮数和向下轮数,因此我们可以忽略这些情况。)

笔记

  1. 我假设 Python 3。
  2. 在典型的台式机或笔记本电脑上,Python 的浮点数将使用 IEEE 754 binary64(“双精度”)浮点格式,整数的真正除法和round函数都将是正确的舍入运算,使用 round-ties-to - 偶数舍入模式。虽然语言本身不能保证这一切,但这种行为非常普遍,我们假设在这个问题中使用了这样一台典型的机器。
  3. 这个问题的灵感来自 Python 错误报告:https ://bugs.python.org/issue41198

标签: pythonfloating-pointroundingieee-754

解决方案


不是答案,只是想充实一下令人费解的地方。这当然不是“随机的”,但注意到这还不够 ;-) 只需查看 2 位数的情况即可:

>>> from decimal import Decimal as D
>>> for i in range(5, 100, 10):
...     print('%2d' % i, D(i / 100))
    
 5 0.05000000000000000277555756156289135105907917022705078125
15 0.1499999999999999944488848768742172978818416595458984375
25 0.25
35 0.34999999999999997779553950749686919152736663818359375
45 0.450000000000000011102230246251565404236316680908203125
55 0.5500000000000000444089209850062616169452667236328125
65 0.65000000000000002220446049250313080847263336181640625
75 0.75
85 0.84999999999999997779553950749686919152736663818359375
95 0.9499999999999999555910790149937383830547332763671875

现在你可以配对i/100(100-i)/100它们的数学和正好是 1。所以这个配对,在上面,5 和 95,15 和 85,等等。5 舍入的确切机器值,而 95 舍入的确切机器值,这是“预期的”:如果真正的和为 1,并且一个加数“向上舍入”,那么另一个肯定“向下舍入”。

但情况并非总是如此。15和85都是向下取整,25和75是混合,35和65是混合,但45和55都是向上取整。

是什么使总的“上升”和“下降”案例完全平衡?Mark 表明它们对10**310**7和是有效的10**9,我也验证了指数 2、4、5、6、8、10 和 11 的精确平衡。

一个令人费解的线索

这是非常微妙的。与其除以10**n,不如乘以它的倒数。与上面的对比:

>>> for i in range(5, 100, 10):
...     print('%2d' % i, D(i * (1 / 100)))

 5 0.05000000000000000277555756156289135105907917022705078125
15 0.1499999999999999944488848768742172978818416595458984375
25 0.25
35 0.350000000000000033306690738754696212708950042724609375
45 0.450000000000000011102230246251565404236316680908203125
55 0.5500000000000000444089209850062616169452667236328125
65 0.65000000000000002220446049250313080847263336181640625
75 0.75
85 0.84999999999999997779553950749686919152736663818359375
95 0.95000000000000006661338147750939242541790008544921875

现在共有 7 个(而不是 5 个)案例。

对于10**3, 64(而不是 50)向上取整;对于10**4, 828 (而不是 500), 对于10**5, 9763 (而不是 5000); 等等。因此,在计算中遭受不超过一个舍入误差i/10**n至关重要的。


推荐阅读