在纯数学中,当我们谈论整数除法 a ÷ b a \div b a÷b ( a a a 是被除数, b b b 是除数,且 b ≠ 0 b \not = 0 b=0) ,结果包含一个商(quotient) q q q 和一个余数(remainder) r r r,它们满足以下的关系:
a = b × q + r a = b \times q + r a=b×q+r
并且 r r r 满足 0 ≤ ∣ r ∣ < ∣ b ∣ 0 \le\lvert r\rvert\lt\lvert b\rvert 0≤∣r∣<∣b∣ . 也就是说,余数的绝对值小于除数的绝对值,并且通常要求余数是非负的,即 0 ≤ r < ∣ b ∣ 0\le r\lt\lvert b\rvert 0≤r<∣b∣ .
在这个定义下,「取余」和「取模」是等同的,目的都是求「满足上述等式的非负余数 r r r」.
在计算机领域中,取余运算(Remainder Operation)和取模运算(Modulo Operation)并不完全相同。
由于历史原因和不同语言的设计选择,取余和取模在处理负数时会出现差异,通常,取余的结果符号同被除数,取模的结果总是非负的。而当 a a a 和 b b b 都是正数时,所有编程语言和计算器对 a % b
或 a mod b
的结果都是一致的,结果等于上述数学定义中的非负余数。
C、C++、Java、JavaScript、Go等采取取余运算;
Python、Ruby等采取取模运算;
而在某些语言中,取余和取模都有,比如Matlab、Julia。
下面对比一下 %
运算符在Python、Java、C和C++中的区别。
%
运算符先上代码示例,右边为输出结果:
print(10 % 3) # 1
print(-10 % 3) # 2
print(10 % -3) # -2
print(-10 % -3) # -1
Python的运算方式为:a % b = a - b * floor(a / b)
,其中 floor()
向下取整。
因此上面的代码示例是这样计算的:
10 % 3 = 10 - 3 * floor(10 / 3) = 10 - 3 * 3 = 1
-10 % 3 = -10 - 3 * floor(-10 / 3) = -10 - 3 * (-4) = 2
10 % -3 = 10 - (-3) * floor(10 / -3) = 10 - (-3) * (-4) = -2
-10 % -3 = -10 - (-3) * floor(-10 / -3) = -10 - (-3) * 3 = -1
结果符号与除数相同。
前面我们说到,通常,取余的结果符号同被除数,取模的结果总是非负的,但这里也有负值,这是为什么?原因是一般情况下,我们总是使用正除数,这一点会在文章最后加以简单说明。
代码示例:
System.out.println(10 % 3); // 1
System.out.println(-10 % 3); // -1
System.out.println(10 % -3); // 1
System.out.println(-10 % -3); // -1
Java的运算方式为:a % b = a - b * trunc(a / b)
,其中 trunc()
向零取整。
因此上面的代码示例是这样计算的:
10 % 3 = 10 - 3 * trunc(10 / 3) = 10 - 3 * 3 = 1
-10 % 3 = -10 - 3 * trunc(-10 / 3) = -10 - 3 * (-3) = -1
10 % -3 = 10 - (-3) * trunc(10 / -3) = 10 - (-3) * (-3) = 1
-10 % -3 = -10 - (-3) * trunc(-10 / -3) = -10 - (-3) * 3 = -1
结果符号和被除数相同。
代码示例:
printf("%d\n", 10 % 3); // 1
printf("%d\n", -10 % 3); // -1
printf("%d\n", 10 % -3); // 1
printf("%d\n", -10 % -3); // -1
运算方式和Java一样,不再赘述。
早期C语言中,负数的
%
运算并未统一,未对a/b
取整方向做强制规定,由编译器或底层硬件平台自行决定。C99/C++11及之后,统一规定a/b
向零取整。
模运算的经典使用场景中,除数一般都是正数,比如数论的同余关系、时间计算、循环访问数组、密码学的一些处理等,除数的物理意义往往是周期、长度等,这些都是正数。
个人笔记,能力有限可能有错漏,欢迎大家交流指正!能帮到你我会非常开心~