Python中//地板除法与floor(除法)结果不一致的原因及是否为Bug?
Python中
//地板除法与floor函数的差异原因 Python文档中将整数除法运算符//称为「地板除法」,但实际使用中它与math.floor、numpy.floor的结果存在差异,例如:
>>> numpy.floor(1/0.2) 5.0 >>> float(math.floor(1/0.2)) 5.0 >>> 1//0.2 4.0
进一步测试还能发现更多细节:
>>> numpy.floor(1/0.20000000000000001) 5.0 >>> numpy.floor(1/0.2000000000000001) 4.0
>>> 1//0.19999999999999999 5.0 >>> 1//0.199999999999999999 4.0
使用的是Anaconda提供的标准CPython,下面分析差异产生的原因及是否属于Bug:
差异产生的核心原因
1. 浮点数的二进制精度限制
十进制中的0.2无法被二进制浮点数精确表示,双精度浮点数(Python默认的浮点数类型)会用一个略小于0.2的近似值来存储它。同理,很多十进制小数在二进制中都是无限循环的,只能存储近似值,这是所有基于IEEE 754标准的浮点数的共性问题。
2. //与floor函数的实现逻辑差异
math.floor/numpy.floor:直接对输入的浮点数值做「向下取整」操作。例如1/0.2的计算结果,因为除数是略小于0.2的近似值,理论上商应该略大于5,但由于双精度浮点数的精度限制,这个略大于5的值无法被精确存储,反而被舍入为5.0,所以floor函数返回5.0。//运算符:它的语义是找到最大的整数n,使得n * b <= a(其中a是被除数,b是除数)。对于1//0.2,计算5 * 0.2的实际值:5乘以那个略小于0.2的近似值,结果是0.9999999999999999,这个值小于1,不满足n*b <=a的条件,因此n只能取4,最终返回4.0。
3. 测试案例的细节解释
0.20000000000000001:这个值超出了双精度浮点数的精度范围,会被舍入为和0.2完全相同的近似值,因此1/0.20000000000000001的结果还是5.0,floor返回5.0;而0.2000000000000001是一个能被区分的、略大于0.2的浮点数,1除以它的结果是略小于5且能被精确存储的数值,所以floor返回4.0。0.19999999999999999:同样因为精度限制,它被舍入为一个略大于0.2的浮点数,此时5 * 这个值 <=1,满足//的条件,所以返回5.0;而0.199999999999999999被解析为另一个略小于0.2的浮点数,5*这个值>1,因此//返回4.0。
是否属于Bug?
这不是Bug,而是//运算符的设计语义决定的。//的核心要求是保证(a // b) * b <= a的关系始终成立,而floor函数的核心是对浮点商做向下取整。两者的设计目标不同,在浮点数场景下出现差异是预期行为,只是这种差异在特定数值下会显得反直觉。
内容的提问来源于stack exchange,提问作者Jonatan Öström
相关产品推荐
相关产品推荐

