原文作者: Guido van Rossum
Why Python's Integer Division Floors
为何Python整除运算采用向下取整的规则
今天(又)有人问我,为什么Python中的整除(integer division)返回值向下取整(floor)而不是像C语言中那样向0取整。
在正整数范围内,两者并无实质差别,例如:
>>> 5//2
2
但是当操作数之一为负时,结果是向下取整的,也就是远离0(接近负无穷方向):
但是当操作数之一为负时,结果是向下取整的,也就是远离0(接近负无穷方向):
>>> -5//2
-3
>>> 5//-2
-3
或许部分人不太适应,数学上有一个较好的解释为何这样做。整除运算(//)和与之密切相关的取模运算(%)满足如下优美的数学关系式(所有变量均为整数):
a/b = q 余数为 r
有
b * q + r = a 而且 0 <= r < b (假设a和b都>=0)
如果希望将这一关系扩展到a为负(b仍为正)的情况,有两个选择:一是q向0取整,r取负值,这时约束关系变为 0 <= abs(r) < b,另一种选择是q向下(负无穷方向)取整,约束关系不变,依然是 0 <= r < b。
或许部分人不太适应,数学上有一个较好的解释为何这样做。整除运算(//)和与之密切相关的取模运算(%)满足如下优美的数学关系式(所有变量均为整数):
a/b = q 余数为 r
有
b * q + r = a 而且 0 <= r < b (假设a和b都>=0)
如果希望将这一关系扩展到a为负(b仍为正)的情况,有两个选择:一是q向0取整,r取负值,这时约束关系变为 0 <= abs(r) < b,另一种选择是q向下(负无穷方向)取整,约束关系不变,依然是 0 <= r < b。
在数学的数论中,数学家总是倾向于第二种选择(参见如下Wikipedia链接)。在Python语言中我也做了同样选择,因为在某些取模操作应用中a取什么符号并不重要。例如从POSIX时间戳(从1970年初开始的秒数)得到其对应当天的时间。因为一天有24*3600 = 86400秒,这一操作就是简单的t % 86400。但是当表达1970年之前的时间,这时是一个负数,向0取整规则得到的是一个毫无意义的结果!而向下取整规则得到的结果仍然是正确的。
另外一个我能想到的应用是计算机图形学中计算像素的位置。我相信这样的应用还有更多。
顺便说一下,b取负值时,仅需要把符号取反,约束关系变为:
0 >= r > b
那么,现在的问题变成,C为啥不采取(Python)这样的选择呢?可能是设计C时硬件不适合这样做,所谓硬件不适合这样做是说指那些最老式的硬件把负数表示为“符号+大小”而不是像现在的硬件用二进制补码表示(至少对整数是用二进制补码)。我的第一台计算机是一台Control Data大型机,它用1的补码来表示整数和浮点数。60个1的序列表示负0!
Tim Peters对Python的浮点数部分洞若观火,对于我想把这一规则推广到浮点数取模运算有些担心。可能他是对的,因为向负无穷取整的规则有可能导致当x是绝对值特别小的负数时x%1.0会丢失精度。但是这还不足以让我对整数取模,也就是//进行修改。
附言:注意我用了//而不是/,这是一个Python 3 语法,而且在Python 2 中也是有效的,它强调了使用者是要进行整除操作。Python 2 中的 / 有可能产生歧义,因为对两个操作数都是整数时或者一个整数一个浮点数或者两个都是浮点数时,返回的结果类型不同。当然,这是另外的故事,详情参见PEP238。
2 条评论:
Typo:
顺便说一下,b[去]负值时,仅需要把符号[去翻],约束关系变为:
我的第一台计算机是一台Control Data大型机,[许多1的补码用来表示整数以及浮点数。60个1模式表示的是负0!]
这样译也许更合适:它用1的补码来表示整数和浮点数。60个1的序列表示负0!
@y, 多谢。我不熟悉blogspot的使用,今天才发现这个回复被自动放在spam里面了。。。
发表评论