引子

在很多博客和视频中,不少人都将 取余(rem)取模(mod) 混为一谈,虽然这两个是同一种运算,实际上他们区别很大,而编程语言的 % 符号,不同语言有着不同的处理方式,比如在 C/C++ 中,这个符号代表 “取余”,而在 Python 中,这个符号代表着 “取模”。

为什么同一种运算会出现区别呢?

因为在被除数和除数的符号不同时,余数的符号会产生歧义,可正可负。

取模定理和求余定理

前提:设a=kb+ra = kb + r, 且满足0ak0 \leq \left | a\right | \leq \left | k\right |

给定aakk,求mod(a,k)mod(a, k)rem(a,k)rem(a, k)

显然,对于满足上述两个前提条件,如果aa 无法整除kk,则存在两对(b,r)(b, r),其中一对的rr 为正数(正余数),另一对rr 为负数 (负余数) ,我们也可以用一个时钟从0点顺时针和逆时针走到某一点的两种方案来理解。

结果:正因为上述歧义,不同语言的取模的定义可能不一样,最常见的是:

取模:kk 更趋于负无穷大时的rr,即mod(a,b)mod(a, b)

求余:kk 更趋于 0 是的rr, 即rem(a,b)rem(a, b)

举例

例1

mod(7,3)=1mod(7, 3) = 1rem(7,3)=1rem(7, 3) = 1

此时的两对(b,r)(b, r) 分别为:

  • (2,1)(2, 1) , 即7=23+17 = 2 * 3 + 1
  • (3,2)(3, -2) ,即7=33+(2)7= 3 * 3 + (-2)

因为b1=2,  b2=3b1 = 2, \ \ b2= 3

所以,b1b1 更趋近于负无穷大,所以取模操作时的rr 取第1组,r=1r = 1

同理,因为b1b1 更趋近于负无穷大,所以取余操作的rr 也取第一组,r=1r = 1


例2

mod(7,3)=2mod(7, -3) = -2rem(7,3)=1rem(7, -3) = 1

此时的两对(b,r)(b, r) 分别为:

  • (2,1)(-2, 1) , 即7=(2)(3)+17 = (-2) * (-3) + 1
  • (3,2)(-3, -2) ,即7=(3)(3)+(2)7= (-3) * (-3) + (-2)

因为b1=2,  b2=3b1 = -2, \ \ b2= -3

所以,b2b2 更趋近于负无穷大,所以取模操作时的rr 取第2组,r=2r = -2

而因为b1b1 更趋近于 0 ,所以取余操作时的rr 取第二组,r=1r = 1


例3

mod(7,3)=2mod(-7, 3) = 2rem(7,3)=1rem(-7, 3) = -1

此时的两对(b,r)(b, r) 分别为:

  • (2,1)(-2, -1) , 即7=(2)3+(1)7 = (-2) * 3 + (-1)
  • (3,2)(-3, 2) ,即7=(3)3+27= (-3) * 3 + 2

因为b1=2,  b2=3b1 = -2, \ \ b2= -3 , 所以b2b2 更趋近于负无穷大,所以取模操作时的rr 取第2组,r=2r = 2

而因为b1b1 更趋近于 0 ,所以取余操作时的rr 取第二组,r=1r = -1


例4

mod(7,3)=1mod(-7, -3) = -1rem(7,3)=1rem(-7, -3) = -1

此时的两对(b,r)(b, r) 分别为:

  • (2,1)(2, -1) , 即7=(2)(3)+(1)7 = (2) * (-3) + (-1)
  • (3,2)(3, 2) ,即7=3(3)+27= 3 * (-3) + 2

因为b1=2,  b2=3b1 = 2, \ \ b2= 3, 所以,b1b1 更趋近于负无穷大,所以取模操作时的rr 取第1组,r=1r = -1

而因为b1b1 更趋近于 0 ,所以取余操作时的rr 也取第一组,r=1r = -1


编程语言中的 % 符号

C/C++ 中: % 符号代表取余

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
std::cout << (7 % 3) << std::endl;
std::cout << (7 % -3) << std::endl;
std::cout << (-7 % 3) << std::endl;
std::cout << (-7 % -3) << std::endl;
}

// output:
1
1
-1
-1

Java 中: % 符号代表取余

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String []args) {
System.out.println(7 % 3);
System.out.println(7 % -3);
System.out.println(-7 % 3);
System.out.println(-7 % -3);
}
}
// output:
1
1
-1
-1

Rust 中:% 符号代表取余

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
println!("{}", 7 % 3);
println!("{}", 7 % -3);
println!("{}", -7 % 3);
println!("{}", -7 % -3);

}
// output:
1
1
-1
-1

Python 中:% 符号代表取模

1
2
3
4
5
6
7
8
9
print(7 % 3)
print(7 % -3)
print(-7 % 3)
print(-7 % -3)
// output:
1
-2
2
-1

Golang 中:% 符号代表取模

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
fmt.Println(7 % 3)
fmt.Println(7 % -3)
fmt.Println(-7 % 3)
fmt.Println(-7 % -3)

}

剩下的语言就不一一列举了,大部分语言的 % 语义都是取余。

取余运算满足该等式:m % n = m - n * (m / n)

如C语言中: 5 / -3 = -1 5 % (-3) = 5 - (-3) *(5 / -3) = 2