为什么Python的取模操作符 (%) 不符合欧几里得定义?

3 投票
1 回答
1032 浏览
提问于 2025-04-18 14:00

欧几里得的定义是这样的:

给定两个整数 a 和 b,且 b 不等于 0,存在唯一的整数 q 和 r,使得 a = bq + r,并且 0 ≤ r < |b|,这里 |b| 表示 b 的绝对值。

根据下面的观察:

>>> -3 % -2   # Ideally it should be (-2 * 2) + 1
-1
>>> -3 % 2    # this looks fine, (-2 * 2) + 1 
1
>>> 2 % -3    # Ideally it should be (-3 * 0) + 2
-1

看起来 % 这个运算符的规则有些不同。

  • 链接1 没什么帮助,
  • 链接2 给出了递归的答案,因为我不太明白 % 是怎么工作的,所以理解 (a // b) * b + (a % b) == a 是怎么成立的也很困难。

我的问题是:

我该如何理解 Python 中取模运算符的行为?我对其他语言中 % 运算符的工作方式也不太了解。

1 个回答

5

整数除法和取模运算的行为在一篇关于Python历史的文章中有解释,具体内容可以参考这篇文章:为什么Python的整数除法向下取整。我来引用相关的部分:

如果其中一个操作数是负数,结果会向下取整,也就是朝着负无穷的方向取整:

>>> -5//2
-3
>>> 5//-2
-3

这让一些人感到困扰,但其实有很好的数学原因。整数除法运算(//)和取模运算(%)是相互关联的,并且满足一个很好的数学关系(所有变量都是整数):

a/b = q,余数为r

满足以下条件:

b*q + r = a0 <= r < b

(假设ab都是大于等于0的)。

如果你想让这个关系适用于负的a(保持b为正),你有两个选择:如果你把q向零截断,r会变成负数,这样不等式就变成了0 <= abs(r);否则,你可以把q向负无穷取整,这样不等式保持为0 <= r < b

在数学数论中,数学家们总是更倾向于后者的选择(可以参考维基百科)。对于Python,我也做了同样的选择,因为在某些有趣的取模运算中,a的符号并不重要。

顺便提一下,对于负的b,一切都会翻转,不等式变为:

0 >= r > b.

换句话说,Python在某些情况下选择了“打破”欧几里得定义,以获得在“有趣的情况下”更好的表现。特别是,负的a被认为是有趣的,而负的b则被认为不那么重要。这是一个完全任意的选择,不同编程语言之间并不一致。

需要注意的是,许多常见的编程语言(如C、C++、Java等)并不满足欧几里得不等式,往往在比Python更多的情况下(例如,即使b是正数时)。其中一些语言甚至对余数的符号没有任何保证,留给实现者自行定义。


附带说明一下:Haskell提供了两种类型的取模和除法。标准的欧几里得取模和除法分别称为remquot,而向下取整的除法和“Python风格”的取模则称为moddiv

撰写回答