为什么Ruby';s Float#round行为不同于Python';s

为什么Ruby';s Float#round行为不同于Python';s,python,ruby,rounding,floating-accuracy,Python,Ruby,Rounding,Floating Accuracy,“”观察到Python循环浮动如下: >>> round(0.45, 1) 0.5 >>> round(1.45, 1) 1.4 >>> round(2.45, 1) 2.5 >>> round(3.45, 1) 3.5 >>> round(4.45, 1) 4.5 >>> round(5.45, 1) 5.5 >>> round(6.45, 1) 6.5 >>

“”观察到Python循环浮动如下:

>>> round(0.45, 1)
0.5
>>> round(1.45, 1)
1.4
>>> round(2.45, 1)
2.5
>>> round(3.45, 1)
3.5
>>> round(4.45, 1)
4.5
>>> round(5.45, 1)
5.5
>>> round(6.45, 1)
6.5
>>> round(7.45, 1)
7.5
>>> round(8.45, 1)
8.4
>>> round(9.45, 1)
9.4
01111111101 1100110011001100110011001100110011001100110011001101 
公认的答案证实了这是由于浮点的二进制表示不准确造成的,这是合乎逻辑的

假设Ruby浮动和Python的一样不准确,为什么Ruby浮动会像人类一样?鲁比作弊吗

1.9.3p194 :009 > 0.upto(9) do |n|
1.9.3p194 :010 >     puts (n+0.45).round(1)
1.9.3p194 :011?>   end
0.5
1.5
2.5
3.5
4.5
5.5
6.5
7.5
8.5
9.5

摘要

两种实现都面对相同的s

Ruby通过简单的操作(乘以十的幂、调整和截断)直接对浮点数进行操作

Python使用David Gay的复杂算法将二进制浮点数转换为字符串,该算法生成与二进制浮点数完全相等的最短十进制表示形式。这不会进行任何额外的舍入,它是到字符串的精确转换

Python使用最短的字符串表示形式,使用精确的字符串操作将小数舍入到适当的位数。浮点到字符串转换的目标是尝试“撤消”某些二进制浮点表示错误(即,如果输入6.6,Python将在6.6上循环,而不是在6.599999996上循环)

此外,Ruby在舍入模式上与Python的某些版本有所不同:从零舍入到半偶数舍入

细节

Ruby不会作弊。它以普通的二进制浮点数开始,就像Python一样。因此,它面临着一些相同的挑战(例如3.35表示为略高于3.35,4.35表示为略低于4.35):

查看实现差异的最佳方法是查看底层源代码:

以下是Ruby源代码的链接:

Python源代码从这里开始: 在这里结束:

后者有一个广泛的注释,揭示了两种实现之间的差异:

其基本思想非常简单:将double转换为 使用_Py_dg_dtoa的十进制字符串,然后转换该十进制字符串 回到双人赛,有一个小困难: Python2.x期望round在零的一半处进行round,而 _Py_-dg_-dtoa的结果是从一半到一半。因此,我们需要一些方法来检测和纠正一半的情况

检测:中间值的形式为k*0.5*10**-ndigit 一些奇数整数k,或者换句话说,有理数x正好是 如果其2-估值为 确切地说,NDIGTS-1及其5-估值至少为 -ndigits。对于ndigits>=0,二进制浮点x自动满足后一个条件,因为任何此类浮点都具有非负性 5-赋值。对于0>ndigits>=-22,x需要是一个整数 5**-ndigit的倍数;我们可以使用fmod检查这一点。For-22> 对于ndigit,没有半途而废的情况:5**23需要54位来表示 准确地说,因此对于n>=23,任何0.5*10**n的奇数倍数至少需要 精确表示的精度为54位

更正:处理中途退场案件的一个简单策略是 (仅适用于中途情况)调用_Py_dg_dtoa,参数为 ndigits+1而不是ndigits 小数),手动将结果字符串四舍五入,然后再转换回 使用_Py_dg_strod

简而言之,Python2.7竭尽全力精确地遵循一条规则

在Python3.3中,要准确地遵循一条规则,它需要同样长的长度

这里有一点关于该函数的附加细节。Python调用float to string函数是因为它实现了一种算法,该算法可以在相同的备选方案中提供尽可能短的字符串表示形式。例如,在Python 2.6中,数字1.1显示为1.10000000000001,但在Python 2.7及更高版本中,它只是1.1“人们期望的结果”不放弃准确性

该字符串转换算法倾向于弥补困扰二进制浮点数上任何round()实现的一些问题(即,它从4.35开始减少4.35的舍入,而不是从4.34999999999999447286321199499070644378662109375开始)


Python函数和Ruby round()函数之间的本质区别在于舍入模式(舍入一半偶数与舍入远离零)

Python:转换为十进制,然后四舍五入

Ruby:四舍五入,然后转换为十进制

Ruby从原始浮点位字符串取整,但在使用10n对其进行操作后。如果不仔细观察,就看不到原始二进制值。这些值是不精确的,因为它们是二进制的,我们习惯于用十进制写,而且几乎所有的十进制小数字符串都是不精确的kely to write没有一个与基2分数字符串完全等价的字符串

特别是,0.45看起来如下所示:

>>> round(0.45, 1)
0.5
>>> round(1.45, 1)
1.4
>>> round(2.45, 1)
2.5
>>> round(3.45, 1)
3.5
>>> round(4.45, 1)
4.5
>>> round(5.45, 1)
5.5
>>> round(6.45, 1)
6.5
>>> round(7.45, 1)
7.5
>>> round(8.45, 1)
8.4
>>> round(9.45, 1)
9.4
01111111101 1100110011001100110011001100110011001100110011001101 
十六进制,即
3fdccccd.

它以二进制形式重复,第一个未表示的数字是
0xc,
,巧妙的十进制输入转换将最后一个小数位数精确舍入为
0xd

这意味着在机器内部,该值大约比
0.45
大1/250。这显然是一个非常非常小的数字,但它足以导致默认的舍入最近算法向上舍入,而不是向偶数的平局断路器舍入

Python和Ruby都可能不止一次地舍入,因为每个操作都有效地舍入到最低有效位

我不确定我是否同意Ruby做了人类会做的事情。我认为Python近似于十进制算法。Python(取决于版本)对十进制字符串应用最接近的舍入,Ruby对计算出的二进制值应用最接近的舍入算法

请注意,我们可以
irb(main):003:0> puts 9.45.round(1)
9.5
>>> round(9.45, 1)
9.4
>>> round(9.45*10)/10
9.5