为什么 Python 的负整数除法结果和 C 不同
对于整数-5除以2,在 Python 中的结果是-3,但是在 C 中是-2。如果扩展到其他几种常见的语言,可以看到和 C 一致的比较多:
| 语言 | 结果 |
|---|---|
| C | -2 |
| C++ | -2 |
| Java | -2 |
| C# | -2 |
| Rust | -2 |
| Go | -2 |
| Python | -3 |
| Ruby | -3 |
区别在于对于结果-2.5是选择向0取整还是向负无穷取整,Python 和 Ruby 选择了后者。
对于整数 a 和 n,记 a 除以 n 的结果是 q,余数是 r,则有:a = n * q + r,其中 |r| < |n|。在数论中,r 始终是正数,但是不同的编程语言各自有不同的实现。
In number theory, the positive remainder is always chosen, but in computing, programming languages choose depending on the language and the signs of a or n.
编程语言实现
Truncated division
很多语言采用这种实现,约定 ,其中 trunc 表示向0取整,代表语言如 Java。
Floored division
Donald Knuth 提倡这种实现,约定 ,即向下取整,代表语言如 Python。
Euclidean division
Raymond T. Boute 则提倡这种实现,约定:
即根据 n 的正负号来判断是向下取整还是向上取整,代表语言如 ABAP。
Rounded division
这是 Common Lisp 和 IEEE 754 采用的实现,约定 ,其中 round 使用 rounding half to even,即在常规的取整之外,对于1.5,2.5,x.5这样的数字取整到最近的偶数,例如6.5取整到6,7.5取整到8。
Ceiling division
这是 Common Lisp 提供的另一种实现,约定 ,即向上取整。
Python 实现
回到 Python,很难说上述哪种实现一定最优,Python 的作者提到采用 floored division 是因为对于某些应用来说,如果取模运算返回负数没有意义。例如,给定一个 POSIX timestamp,如何返回该天的时间部分,即时分秒?因为一天有86400秒,假设时间戳是 t,那么 t % 86400 就表示该天过了多少秒,就可以进一步转化为时分秒。而对于在 1970-01-01T00:00:00Z 之前的日期,t 则是负数,采用 floored division 的情况下 t % 86400 依然返回正数,并且结果也是正确的,而 truncated division 则返回负数,需要应用程序进一步处理。
不过,一种编程语言中不一定只提供一种实现,其他实现可以借助函数库。例如,Python 中 -5 % 2 结果是1,实现方式为 floored division,但是 math.fmod(-5, 2) 结果是-1,实现方式为 truncated division。