启程:算术与数系的基石
整数城的清晨总是充满了数字的低语,但今天,广场上的时钟却停在了“无”的刻度上。零——这个最年轻也最被误解的居民——发现自己成了唯一清醒的存在。所有的数字,从一到无穷,都失去了它们的数值,只剩下空洞的符号在街上游荡。一不再是一,二不再是二,整个计数系统崩溃了。零站在中央广场,看着昔日的邻居们茫然无措,心中涌起一股重建的使命感。这不是第一次危机,但可能是最根本的一次:如果连“多少”都无法定义,文明将退回混沌。零知道,要恢复整数城的秩序,必须从最原始的起点开始,一步步重新建立数的意义。这不仅是故事的开端,也是我们探索算术与数系基石的旅程。在现实世界中,我们的数学体系同样建立在看似平凡却深刻的公理之上;当计算机用二进制处理信息,当程序员处理大整数运算,我们都在不知不觉中使用着这些基石。本章将带你从零出发,像零一样,亲手构建计数系统,理解自然数、整数、有理数如何层层扩展,并揭示四则运算背后的代数结构。通过Python实践,你将看到抽象理论如何变为运行中的代码。
皮亚诺公理:从无到有的游戏规则
零的第一个问题是:如何定义“一”?在没有预设的情况下,不能假设我们已经知道1, 2, 3是什么。意大利数学家朱塞佩·皮亚诺(Giuseppe Peano)在19世纪末提出了五条公理,为自然数提供了严格的基础。这些公理不定义自然数是什么,而是规定它们必须满足的性质,就像游戏规则一样,任何满足这些规则的系统都可以被视为自然数模型。
公理1(0是自然数):0是自然数。注意,皮亚诺 originally 以1为起点,但现代计算机科学常用0作为起点,更符合索引习惯。我们用符号 $ \mathbb{N} $ 表示自然数集合,公理1断言 $ 0 \in \mathbb{N} $。
公理2(后继函数):每个自然数 $ n $ 都有一个后继 $ S(n) $,且 $ S(n) $ 也是自然数。后继函数 $ S $ 可以理解为“下一个”操作。例如,$ S(0) $ 就是1,$ S(1) $ 就是2。但公理本身不指定 $ S(0)=1 $,它只保证存在一个后继对象。这允许我们抽象地构建链条:0, S(0), S(S(0)), ...。
公理3(0不是任何数的后继):对所有 $ n \in \mathbb{N} $,有 $ S(n) \neq 0 $。这防止了循环,确保链条从0开始向前延伸,不会绕回。
公理4(后继函数单射):若 $ S(m) = S(n) $,则 $ m = n $。这意味着不同的数有不同的后继,保证了每个数在链条中位置唯一。
公理5(数学归纳法公理):如果集合 $ K \subseteq \mathbb{N} $ 满足:(a) $ 0 \in K $,(b) 若 $ n \in K $ 则 $ S(n) \in K $,那么 $ K = \mathbb{N} $。这是最强的公理,它允许我们证明关于所有自然数的命题。例如,要证明某性质P对所有自然数成立,只需证明P(0)成立,且若P(n)成立则P(S(n))成立。
这五条公理看似简单,却足以推导出所有算术。让我们看看如何定义加法和乘法。首先,我们定义递归关系:
- 加法:
$ a + 0 = a $ $ a + S(b) = S(a + b) $
- 乘法:
$ a \times 0 = 0 $ $ a \times S(b) = a \times b + a $
例如,计算 $ 2 + 3 $: $ 2 + 3 = 2 + S(2) = S(2 + 2) = S(2 + S(1)) = S(S(2 + 1)) = S(S(2 + S(0))) = S(S(S(2 + 0))) = S(S(S(2))) = S(S(3)) = 5 $。
通过归纳法,可以证明加法和乘法的性质:交换律、结合律、分配律。例如,加法交换律 $ a + b = b + a $ 的证明:对b归纳。基础步:$ a + 0 = a = 0 + a $(需先证 $ 0 + a = a $,同样归纳)。归纳步:假设 $ a + b = b + a $,则 $ a + S(b) = S(a + b) = S(b + a) = S(b) + a $,但需证明 $ S(b) + a = S(b + a) $,这又需对a归纳。完整证明需要细致处理,但公理提供了严谨框架。
Peano axioms provide a foundation where numbers are not assumed but constructed. The successor function $ S $ generates the infinite chain: $ 0, S(0), S(S(0)), \dots $. Axiom 5 (induction) is crucial: it lets us extend properties from the base case to all numbers. Without it, we could only reason about finitely many numbers. In computer science, induction mirrors loop invariants: proving a property holds initially and is preserved by each iteration.
公理的实际意义:为什么需要这么严格?
你可能觉得皮亚诺公理 pedantic——我们从小就知数数。但严格公理化避免了隐含假设导致的悖论。例如,历史上,人们曾认为“所有性质都可定义”是显然的,直到罗素悖论出现。在算术中,公理化确保我们讨论的“自然数”是唯一的(在等价的公理系统下),即任意两个满足皮亚诺公理的模型都同构。这意味着无论用集合论构造(如冯·诺依曼定义:0=∅, 1={∅}, 2={∅,{∅}}...)还是其他方式,得到的自然数结构本质相同。
对于计算机,皮亚诺公理直接影响数据类型设计。函数式语言如Haskell用`data Nat = Zero | Succ Nat`直接编码公理:`Zero`对应0,`Succ`对应后继。递归函数自然对应归纳证明。例如,加法实现:
```haskell add Zero n = n add (Succ m) n = Succ (add m n) ```
这 exactly 匹配公理定义。通过这种方式,程序正确性可借助归纳法证明。
数系的扩展:从自然数到整数与有理数
自然数 $ \mathbb{N} $ 足以计数,但无法表示“欠债”或“一半”。整数城需要负数和小数,因此零必须扩展数系。
整数的构造:等价类方法
整数 $ \mathbb{Z} $ 可以视为自然数对的集合,其中一对 $ (a, b) $ 代表“a 减 b”。但不同对可能表示同一整数,例如 (5,3) 和 (7,5) 都代表2。我们定义等价关系: $ (a, b) \sim (c, d) $ 当且仅当 $ a + d = b + c $。 等价类 $ [(a,b)] $ 就是整数 $ a - b $。例如,$ [(5,3)] = [(7,5)] = 2 $,$ [(3,5)] = -2 $。
加法定义为:$ [(a,b)] + [(c,d)] = [(a+c, b+d)] $。 验证良定义:若 $ (a,b)\sim(a',b') $ 且 $ (c,d)\sim(c',d') $,则 $ (a+c, b+d) \sim (a'+c', b'+d') $,因为 $ (a+b')+(c+d') = (a'+b)+(c'+d) $ 由假设可得。 乘法定义为:$ [(a,b)] \times [(c,d)] = [(ac+bd, ad+bc)] $。这对应于 $ (a-b)(c-d) = ac - ad - bc + bd $,但因为我们用对表示,需要调整。
零元是 $ [(0,0)] $,但注意 $ [(k,k)] $ 都是零等价类,因为 $ k+k = k+k $。负元:$ -[(a,b)] = [(b,a)] $。
为什么用等价类? 它避免了先定义负数再定义运算的循环。我们完全在自然数范围内构造整数,然后赋予运算。这体现了数学的构造性:新对象由旧对象定义,运算需证明与代表元选择无关(良定义)。
有理数的构造:分数的严谨化
有理数 $ \mathbb{Q} $ 是整数的分数形式 $ p/q $,$ q \neq 0 $。但同样,不同分数可能相等,如 $ 1/2 = 2/4 $。我们用等价类:有序对 $ (p, q) $,$ q \neq 0 $,等价关系 $ (p,q) \sim (r,s) $ 当且仅当 $ ps = qr $。等价类 $ [(p,q)] $ 表示有理数 $ p/q $。
加法:$ [(p,q)] + [(r,s)] = [(ps + qr, qs)] $。 乘法:$ [(p,q)] \times [(r,s)] = [(pr, qs)] $。 负元:$ -[(p,q)] = [(-p, q)] $。 倒数:若 $ p \neq 0 $,$ [(p,q)]^{-1} = [(q,p)] $。
验证良定义需要用到整数的性质。例如,若 $ (p,q)\sim(p',q') $ 且 $ (r,s)\sim(r',s') $,则 $ (ps+qr, qs) \sim (p's'+q'r', q's') $,这需要 $ ps = qp' $ 和 $ rs = qs' $ 等条件,通过代数运算可得。
构造的意义:有理数域是第一个构成域(field)的数系,即每个非零元素有乘法逆元,且加法、乘法满足交换、结合、分配律。域的性质支持四则运算自由进行(除零外),是代数方程求解的基础。
数系扩展的层次结构
我们可以用图表展示数系的包含关系:
```mermaid graph TD A[自然数 ℕ] --> B[整数 ℤ] B --> C[有理数 ℚ] C --> D[实数 ℝ] D --> E[复数 ℂ] ```
每一步扩展都解决前一步的不足:自然数无减法逆元 → 整数;整数无除法逆元 → 有理数。但有理数仍有“空隙”(如√2不是有理数),需实数填补;实数无法解所有多项式方程(如$x^2+1=0$),需复数。这种扩张模式在代数中反复出现:通过添加元素使结构更丰富。
四则运算的代数结构:群、环、域的雏形
当零重建计数系统时,他注意到不同数系上的运算形成不同的代数结构。这些结构不依赖具体数字,而是关注运算的性质。
群(Group):对称性的语言
群是一个集合 $ G $ 配上一个二元运算 $ * $,满足: 1. 封闭性:$ a, b \in G \Rightarrow a * b \in G $。 2. 结合律:$ (a * b) * c = a * (b * c) $。 3. 单位元:存在 $ e \in G $ 使得 $ e * a = a * e = a $。 4. 逆元:每个 $ a \in G $ 有 $ a^{-1} \in G $ 使得 $ a * a^{-1} = e $。
若 additionally $ a * b = b * a $,则称交换群(阿贝尔群)。
例子:
- 整数加法 $ (\mathbb{Z}, +) $ 是交换群:单位元0,逆元为负。
- 非零有理数乘法 $ (\mathbb{Q}^*, \times) $ 是交换群:单位元1,逆元为倒数。
- 但自然数加法 $ (\mathbb{N}, +) $ 不是群,因为缺乏逆元(负数不在自然数中)。它是交换幺半群(有单位元、结合、交换,但无逆元)。
在整数城故事中,当数字失窃后,零需要恢复加法群的结构:每个数字都应能找到“抵消”它的伙伴。负数就是正数的逆元。
环(Ring):加减乘的协调
环是集合 $ R $ 配备两个运算 $ + $ 和 $ \times $,满足:
- $ (R, +) $ 是交换群。
- $ (R, \times) $ 是幺半群(有单位元1,结合)。
- 分配律:$ a \times (b + c) = a \times b + a \times c $,$ (a + b) \times c = a \times c + b \times c $。
若乘法也交换,则称交换环。
例子:
- 整数环 $ (\mathbb{Z}, +, \times) $:加法群,乘法幺半群(单位元1),分配律成立。
- 多项式环:系数在某个环上的多项式,加法和乘法按规则进行。
- 但自然数 $ \mathbb{N} $ 不是环,因为加法群不成立(无逆元)。它只是半环(加法幺半群,乘法幺半群,分配律)。
环的结构允许我们同时处理加法和乘法,但除法不一定可行。整数城在恢复整数后,就拥有了一个环:数字可以相加、相乘,且乘法对加法分配。
域(Field):四则运算的自由
域是交换环,其中每个非零元素有乘法逆元。即 $ (R \setminus \{0\}, \times) $ 是交换群。
例子:
- 有理数域 $ (\mathbb{Q}, +, \times) $:加法群,非零乘法群,分配律。
- 实数域、复数域。
- 但整数环不是域,因为2没有乘法逆元(1/2不是整数)。
域是“数”的理想模型:你可以自由加减乘除(除零外)。有理数城的居民可以平分一个饼,而整数城的居民只能整分。
从结构看数系扩展
- 自然数 $ \mathbb{N} $:加法交换幺半群,乘法交换幺半群,分配律 → 半环。
- 整数 $ \mathbb{Z} $:加法交换群,乘法交换幺半群(有单位元),分配律 → 交换环(整环,无零因子)。
- 有理数 $ \mathbb{Q} $:加法交换群,乘法交换群(非零),分配律 → 域。
每一步都通过添加“缺失的元素”来升级结构:整数添加负数使加法成为群;有理数添加分数使乘法成为群。这种模式在抽象代数中称为“分式化”(localization)或“群完备化”。
计算机科学中的体现:模运算(modular arithmetic)形成有限域(当模数为质数时)。例如,模5的集合{0,1,2,3,4},加法乘法模5,构成域GF(5),用于密码学、纠错码。群论也用于对称加密、哈希函数设计。
Python实践:手动实现大整数运算
计算机内部整数有固定宽度(如32位、64位),但Python的`int`类型支持任意精度,自动处理大数。然而,理解其底层原理有助于优化性能。我们手动实现一个简单的大整数类,用十进制列表存储数字,模拟手算加法。
设计思路
将大整数表示为十进制数字列表,最低位在索引0(小端表示),便于进位处理。例如,1234 表示为 `[4,3,2,1]`。
```python class BigInt: def __init__(self, digits=None):
digits: list of int, least significant digit first
if digits is None: self.digits = [0] else:
移除前导零
while len(digits) > 1 and digits[-1] == 0: digits.pop() self.digits = digits
def __str__(self):
从高位到低位输出
return ''.join(str(d) for d in reversed(self.digits))
def __add__(self, other):
逐位相加,处理进位
result = [] carry = 0 max_len = max(len(self.digits), len(other.digits)) for i in range(max_len): d1 = self.digits[i] if i < len(self.digits) else 0 d2 = other.digits[i] if i < len(other.digits) else 0 total = d1 + d2 + carry carry = total // 10 result.append(total % 10) if carry: result.append(carry) return BigInt(result) ```
测试:
```python a = BigInt([4,3,2,1]) # 1234 b = BigInt([8,7,6]) # 678 print(a + b) # 输出 1912 ```
计算过程: 个位:4+8=12,写2进1; 十位:3+6+1=10,写0进1; 百位:2+7+1=10,写0进1; 千位:1+0+1=2,写2; 结果:`[2,0,0,2]` → 2002?等等,1234+678=1912,我算错了:1234+678=1912,正确计算: 4+8=12 → 2进1; 3+7+1=11 → 1进1; 2+6+1=9 → 9; 1+0=1 → 1;所以数字列表应为 `[2,1,9,1]` 即1912。上述代码中,`a.digits=[4,3,2,1]`对应1234,`b.digits=[8,7,6]`对应678,索引0是个位。循环:i=0: d1=4,d2=8,total=12,carry=1,result=[2];i=1: d1=3,d2=7,total=3+7+1=11,carry=1,result=[2,1];i=2: d1=2,d2=6,total=2+6+1=9,carry=0,result=[2,1,9];i=3: d1=1,d2=0,total=1+0+0=1,carry=0,result=[2,1,9,1];正确。代码无误。
扩展:乘法与更高效表示
加法是线性时间。乘法可用小学算法:对`other`的每位,乘以`self`后移位相加。时间复杂度 $ O(n^2) $。更高效算法有Kara