模运算(Modular Arithmetic)的乐趣

一位读者最近建议我写写模运算(也就是“取余数”)。我之前没怎么考虑过,但意识到模运算非常强大:它应该像加法和乘法一样,成为我们思维工具箱中的一员。

不要直接用公式砸你脸,让我们先探索一个多年来我们已微妙接触过的想法。有一篇 关于模算术的好文章 激发了这篇文章的灵感。

奇数(Odd)、偶数(Even)与三的倍数(Threeven)

在发现整数(1, 2, 3, 4, 5……)后不久,我们意识到它们分为两组:

  • 偶数:能被2整除 (0, 2, 4, 6..)
  • 奇数:不能被2整除 (1, 3, 5, 7…)

为什么这个区别很重要?这是抽象的开始——我们注意到一个数字的 性质(Properties) 特性(比如是偶数还是奇数),而不仅仅是数字本身(“37”)。

这非常重要——它让我们能在更深层次探索数学,并找到数字 类型(Types) 之间的关系,而不仅仅是具体数字。例如,我们可以制定这样的规则:

  • 偶数 x 偶数 = 偶数
  • 奇数 x 奇数 = 奇数
  • 偶数 x 奇数 = 偶数

这些规则是通用的——它们在性质层面起作用。(直觉上,我有个 化学类比 观点:“偶数性”是一些数字拥有的分子,且不能被乘法移除。)

但是奇偶性是一个非常特定的性质:除以2。那么数字3呢?试试这个:

  • “Threeven” 表示一个数能被3整除 (0, 3, 6, 9…)
  • “Throdd” 表示你  能被3整除 (1, 2, 4, 5, 7, 8…)

奇怪但可行。你会注意到两点:有三余数(throdd)的两种类型。像“4”这样的数离三整(threeven)差1(余数为1),而数5差2(余数为2)。

“三整”只是数字的另一个性质。也许不像奇偶性那样立即可用,但它确实存在:我们可以制定像“三整乘三整等于三整”这样的规则,等等。

但这变得疯狂了。我们不能一直创造新词。

认识模(Modulo)

模运算(缩写为“mod”,在许多编程语言中为“%”)是除法后的余数。例如,“5 mod 3 = 2”意味着2是5除以3的余数。

将日常用语转换为数学,“偶数”就是“0 mod 2”——即除以2余数为0。奇数是“1 mod 2”(余数为1)。

为什么这很酷?嗯,我们的“奇/偶”规则变成了这样:

  • 偶数 x 偶数 = 0 x 0 = 0 [偶数]
  • 奇数 x 奇数 = 1 x 1 = 1 [奇数]
  • 偶数 x 奇数 = 0 x 1 = 0 [偶数]

很酷吧?很容易理解——我们把“性质”转换成了实际方程,并发现了一些新事实。

偶数乘偶数乘奇数乘奇数是多少?嗯,就是0 × 0 × 1 × 1 = 0。实际上,你可以看到只要有一个偶数相乘, 任何地方(Anywhere) 整个结果就是零……我是说偶数:)。

时钟数学(Clock Math)

模运算的狡猾之处在于我们已经用它来计时——有时称为“时钟算术”。

例如:现在是7:00(上午/下午不重要)。7小时后时针会在哪里?

嗯。7 + 7 = 14,但我们不能在时钟上显示“14:00”。所以一定是2。我们凭直觉推理,用数学术语来说:

  • (7 + 7) mod 12 = (14) mod 12 = 2 mod 12 [2是14除以12的余数]

方程“14 mod 12 = 2 mod 12”意味着,“14点”和“2点”在12小时制时钟上看起来一样。它们是等价的, 同余用三横等号表示:14 ≡ 2 mod 12。

另一个例子:现在是8:00。25小时后时针会在哪里?

不用把25加到8上,你可能意识到25小时就是“1天+1小时”。所以,时钟会向前走1小时,到9:00。

  • (8 + 25) mod 12 ≡ (8) mod 12 + (25) mod 12 ≡ (8) mod 12 + (1) mod 12 ≡ 9 mod 12

你凭直觉把25转换为1,然后加到8上。

有趣的性质:数学照常成立

以时钟为类比,我们可以判断模运算的规则是否“成立”(它们确实成立)。

加法/减法

假设两个时间在时钟上看起来相同(“2:00”和“14:00”)。如果我们给两者都加上相同的“x”小时,会发生什么?

嗯,它们在时钟上变化相同!2:00 + 5小时 ≡ 14:00 + 5小时 — 两者都会显示7:00。

为什么?嗯,我们从不关心14携带的多余的“12:00”。我们可以直接将5加到两者都有的余数2上,它们前进相同。对于所有同余数(2和14),加法和减法的结果相同。

乘法

乘法是否保持不变更难判断。如果14 ≡ 2 (mod 12),我们可以两边乘以同一个数得到相同的结果吗?

让我们看看——乘以3会发生什么?

嗯,2:00 * 3 ≡ 6:00。但“14:00” * 3 呢?

记住,14 = 12 + 2。所以,我们可以说

  • 14 * 3 = (12 + 2) * 3 = (12 * 3) + (2 * 3) mod 12

第一部分(12 * 3)可以忽略!14携带的“12小时溢出”只是重复了几次。但谁在乎呢?我们反正忽略溢出。

相乘时,只有余数才重要,对于14:00和2:00,余数都是相同的2小时。直观上,这就是我认为乘法不会改变模运算关系的原因(你可以对模关系两边乘以同一个数,得到相同的结果)。参见 上方链接 更严谨的证明——这些是我 直观的铅笔线.

模运算(Modular Arithmetic)的用途

现在是有趣的部分——为什么模算术有用?

简单时间计算

我们直观地这样做,但给它起个名字很好。你有一架下午3点到达的航班,延误了14小时。它将在什么时间降落?

嗯,14 ≡ 2 mod 12。所以我把它看作“2小时加上上午/下午切换”,因此我知道它将是“3 + 2 = 凌晨5点”。

这比简单的模运算符更复杂一些,但原理相同。

将物品放入随机组

假设有一些买了电影票的人,带有确认号。你想把他们分成两组。

你怎么做?“奇数这边,偶数那边”。你不需要知道发出了多少张票(前半部分,后半部分),每个人都能立即算出自己的组(无需联系中央权威),而且随着更多人购票,这个方案仍然有效。

需要3组?除以3取余数(即 mod 3)。你将得到组“0”、“1”和“2”。

在编程中,取模是将项目放入哈希表的方式:如果你的表有N个条目,将项目键转换为数字,进行 mod N,然后将项目放入那个桶(可能在那里维护一个链表)。随着哈希表大小增长,你可以重新计算键的模。

随机选取一个物品

我在现实生活中使用模运算。真的。我们有4个人玩游戏,需要选一个人先开始。 玩模N小游戏! 给人们编号0、1、2和3。

现在每个人都喊“一、二、三、出!”并伸出随机数量的手指。把它们加起来除以4——谁得到正好那个余数谁就先走。(例如:如果手指总和是11,那么编号为“3”的人先走,因为 11 mod 4 = 3)。

它快速且有效。

在循环中运行任务

假设任务需要按特定时间表进行:

  • 任务A每小时运行3次
  • 任务B每小时运行6次
  • 任务C每小时运行1次

如何存储这些信息并制定时间表?一种方法是:

  • 设置一个每分钟运行一次的计时器(将分钟记为“n”)
  • 3次/小时意味着每60/3=20分钟一次。所以任务A在“n % 20 == 0”时运行
  • 任务B在“n % 10 == 0”时运行
  • 任务C在“n % 60 == 0”时运行

哦,你需要任务C1,它每小时运行1次,但不要与任务C同时运行?没问题,让它当“n mod 60 == 1”时运行(仍然是每小时一次,但和C1不同时)。

在脑海中,我看到一个想要以不同间隔“命中”的循环,于是我插入了一个模运算。巧妙之处在于,这些命中可以独立重叠。在这方面有点像XOR(每个XOR都可以分层——但那是另一篇文章了!)。

类似地,编程时你可以通过 if (n % 100 == 0){ print… } 来每隔100条日志打印一次。

这是一种非常灵活、简单的方式让任务按时间表运行。事实上,这就是回答 FizzBuzz合理性检查的方式。如果你的工具箱里没有模运算,这个问题就会棘手得多。

寻找数字的性质

假设我告诉你:

  • a = (47 * 2 * 3)

你能快速推断出什么?嗯,“a”必须是偶数,因为它等于一个乘以2的式子。

如果我还告诉你:

  • a = (39 * 7)

你会犹豫。不是因为你“知道”这两个乘积不同,而是因为一个明显是偶数,另一个是奇数。有问题:a不能同时出现在两个方程中,因为 性质不匹配.

像“偶数”、“三倍数”和“模n”这样的属性,比单个数字更通用,我们可以用来检查一致性。所以我们可以在不知道数字具体是什么的情况下,利用模运算来判断它们是否一致!

如果我告诉你:

  • 3a + 5b = 8
  • 3a + b = 2

这些方程能否用整数求解?让我们看看:

  • 3a + 5b = 8… 我们“取模3”:0 + 2b ≡ 2 mod 3,即 b ≡ 1 mod 3
  • 3a + b = 2… 我们“取模3”:0 + b ≡ 2 mod 3,即 b ≡ 2 mod 3

矛盾了,好朋友们!B不能同时是“1 mod 3”和“2 mod 3”——这就像同时是偶数和奇数一样荒谬!

但有一个陷阱:像“1.5”这样的数既不是偶数也不是奇数——它们不是整数!模运算性质适用于整数,所以我们能说的是b不可能是一个 整数(Integer).

因为,实际上,我们可以解这个方程:

  • (3a + 5b) – (3a + b) = 8 – 2
  • 4b = 6
  • b = 1.5
  • 3a + 1.5 = 2,所以 3a = 0.5,a = 1/6

不要被模运算的力量迷惑!要知道它的局限性:它只适用于整数。

密码学

玩转数字在密码学中有非常重要的应用。这里无法详述,但模运算被用于 Diffie-Hellman密钥交换 — 用于建立SSL连接以加密网络流量。

简明英语

极客们喜欢在日常语境中使用技术词汇。你可能会听到“X is the same as Y modulo Z”,大致意思是“忽略Z,X和Y是相同的。”

例如:

  • b和B相同,只是大小写不同
  • iTouch和iPad相同,只是尺寸不同 ;)

继续向上

思考取模运算符的“实用性”很奇怪——就像有人问指数为什么有用。在日常生活中,它们不太常用,但它是理解世界模式并创造自己模式的工具。

总的来说,我看到几个一般用例:

  • 范围缩减器:输入一个数,mod N,得到一个0到N-1之间的数。
  • 分组分配器:输入一个数,mod N,将其标记为0到N-1的组。任意多方可以约定这个组——例如,知道N=20的不同服务器可以约定ID=57属于哪个组。
  • 属性推导器:根据属性(偶数、三的倍数等)处理数字,并推导出基于属性的原理

我确信还有几十种用法我遗漏了——欢迎在下面评论。祝你数学愉快!

本系列其他文章

  1. 将数字 1 到 100 相加的技巧
  2. 重新思考算术(Arithmetic):可视化指南
  3. 快速洞察:除法的直观含义(Intuitive Meaning of Division)
  4. 快速洞察:减去负数
  5. 平方数(1, 4, 9, 16…)中令人惊讶的规律
  6. 模运算(Modular Arithmetic)的乐趣
  7. 学习如何计数(避免栅栏柱问题(Fencepost Problem))
  8. 数字系统的奇特入门
  9. 再看一眼素数(Prime Numbers)
  10. 黄金比例(Golden Ratio)的直观理解
  11. 数字零(Zero)的不同解释

加入 45 万月度读者

喜欢这篇文章?还有更多内容能帮你建立持久、直观的数学理解。加入通讯以获取额外内容和最新更新。