概括
两种实现都面临围绕二进制浮点数的相同问题。
Ruby 通过简单的操作(乘以 10、调整和截断)直接对浮点数进行操作。
Python 使用 David Gay 的复杂算法将二进制浮点数转换为字符串,该算法产生与二进制浮点数完全相同的最短十进制表示。这不会进行任何额外的舍入,它是对字符串的精确转换。
有了最短的字符串表示,Python 使用精确的字符串运算四舍五入到适当的小数位数。浮点到字符串转换的目标是尝试“撤消”一些二进制浮点表示错误(即,如果您输入 6.6,Python 会在 6.6 上舍入,而不是 6.5999999999999996。
此外,Ruby 在舍入模式方面与某些版本的 Python 不同:从零舍入与半偶数舍入。
细节
鲁比不作弊。它以与 Python 相同的普通旧二进制浮点数开始。因此,它面临一些相同的挑战(例如 3.35 表示略高于3.35,而 4.35 表示略低于4.35):
>>> Decimal.from_float(3.35)
Decimal('3.350000000000000088817841970012523233890533447265625')
>>> Decimal.from_float(4.35)
Decimal('4.3499999999999996447286321199499070644378662109375')
查看实现差异的最佳方法是查看底层源代码:
这是 Ruby 源代码的链接:https ://github.com/ruby/ruby/blob/trunk/numeric.c#L1587
Python 源代码从此处开始:http: //hg.python.org/cpython/file/37352a3ccd54/Python/bltinmodule.c
并在此处结束:http: //hg.python.org/cpython/file/37352a3ccd54/Objects/浮动对象.c#l1080
后者有一个广泛的评论,揭示了两种实现之间的差异:
基本思想非常简单:使用 _Py_dg_dtoa 将双精度数转换并舍入为十进制字符串,然后使用 _Py_dg_strtod 将该十进制字符串转换回双精度数。有一个小困难:Python 2.x 期望 round 做从零开始的半舍入,而 _Py_dg_dtoa 做半舍入到偶数的舍入。所以我们需要一些方法来检测和纠正中途的情况。
检测:对于某个奇数 k,中间值的形式为 k * 0.5 * 10**-ndigits。或者换句话说,如果一个有理数 x 的 2 值正好是 -ndigits-1 并且它的 5 值至少是 -ndigits,那么它正好在 10**-ndigits 的两个倍数之间。对于 ndigits >= 0,二进制浮点 x 自动满足后一个条件,因为任何这样的浮点都具有非负 5 值。对于 0 > ndigits >= -22,x 需要是 5**-ndigits 的整数倍;我们可以使用 fmod 进行检查。对于 -22 > ndigits,没有中途情况:5**23 需要 54 位来精确表示,因此 0.5 * 10**n 的任何奇数倍数对于 n >= 23 至少需要 54 位精度来精确表示。
更正:处理中途情况的一个简单策略是(仅针对中途情况)使用参数 ndigits+1 而不是 ndigits 调用 _Py_dg_dtoa(从而精确转换为十进制),手动舍入结果字符串,然后转换使用 _Py_dg_strtod 返回。
简而言之,Python 2.7 竭尽全力准确地遵循从零开始舍入的规则。
在 Python 3.3 中,准确地遵循从整数到偶数的规则同样需要很长的时间。
这里有一些关于_Py_dg_dtoa函数的额外细节。Python 将 float 调用为字符串函数,因为它实现了一种算法,该算法在相等的备选方案中给出了最短的字符串表示形式。例如,在 Python 2.6 中,数字 1.1 显示为 1.1000000000000001,但在 Python 2.7 及更高版本中,它只是 1.1。David Gay 复杂的 dtoa.c 算法在不放弃准确性的情况下给出了“人们期望的结果”。
该字符串转换算法往往会弥补一些困扰二进制浮点数上的任何实现 round() 的问题(即,它较少舍入 4.35 以 4.35 而不是 4.349999999999999964472863211199499070644378662109375 开头)。
这和舍入模式(半偶数舍入与从零舍入)是 Python 和 Ruby 的 round() 函数之间的本质区别。