摘要
这两种实现都面临着关于二进制浮点数的问题。
Ruby直接对浮点数进行简单操作(乘以10的幂,调整和截断)。
Python使用David Gay的复杂算法将二进制浮点数转换为最短的十进制表示形式,该形式与二进制浮点数完全相等。这不会进行任何额外的舍入,它是一种精确的字符串转换。
有了最短字符串表示形式,Python使用精确的字符串操作将其舍入到适当的小数位数。float-to-string转换的目标是尝试“撤消”一些二进制浮点表示误差(即如果输入6.6,则Python在6.6上进行舍入而不是6.5999999999999996)。
此外,Ruby在舍入模式方面与某些版本的Python不同:向远离零的方向舍入与向最接近偶数的方向舍入。
细节
Ruby 不会作弊。它使用与 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/floatobject.c#l1080
后者有一个详细的注释,揭示了两种实现之间的差异:
基本思想非常简单:使用_Py_dg_dtoa将双精度浮点数转换并四舍五入为十进制字符串,然后使用_Py_dg_strtod将该十进制字符串转换回双精度浮点数。有一个小困难:Python 2.x希望round执行四舍五入到最近的偶数,而_Py_dg_dtoa执行的是四舍五入到最近的一半。因此,我们需要一些方法来检测和纠正中间值的情况。
检测:一个中间值具有k * 0.5 * 10 ** -ndigits的形式,其中k是奇整数。或者换句话说,如果一个有理数x恰好处于两个10 ** -ndigits的倍数之间,则其2-估值恰好为-ndigits-1,其5-估值至少为-ndigits。对于ndigits> = 0,后一个条件对于二进制浮点数x自动满足,因为这样的任何浮点数都具有非负的5-估值。对于0> ndigits> = -22,x需要是5 ** -ndigits的整数倍;我们可以使用fmod来检查这一点。对于-22> ndigits,没有中间值的情况:5 ** 23需要54位才能准确表示,因此任何n> = 23的奇数倍的0.5 * 10 ** n至少需要54位的精度才能准确表示。
纠正:处理中间值的简单策略是(仅对中间值),将_Py_dg_dtoa的参数从ndigits更改为ndigits + 1(因此进行精确的十进制转换),手动四舍五入生成的字符串,然后使用_Py_dg_strtod进行转换。
简而言之,Python 2.7努力遵循
向远离零的方向取整规则。
在Python 3.3中,它同样努力遵循
向偶数取整规则。
下面是关于
_Py_dg_dtoa函数的一些额外细节。Python调用浮点数转字符串函数,因为它实现了一种算法,可以在相等情况下给出最短的字符串表示。例如,在Python 2.6中,数字1.1显示为1.1000000000000001,但在Python 2.7及更高版本中,它只是1.1。
David Gay的复杂dtoa.c算法提供了“人们期望的结果”,同时保证准确性。
那个字符串转换算法倾向于弥补困扰二进制浮点数round()实现的一些问题(即它能使4.35四舍五入后变成4.35而不是4.3499999999999996447286321199499070644378662109375)。
这也是Python和Ruby round()函数之间的本质区别,即舍入模式(一半向偶数舍入还是向零舍入)。
printf "%.20f", 1.45 # => 1.44999999999999995559
1.45.round 1 # => 1.5
- Karoly Horvathround(1.45,1)
返回1.4
,但在Ruby中1.45.round(1)
返回1.5
? - Colonel Panic