PYTHON LESSON 069
超级大数运算
掌握用数组逐位模拟竖式加法与乘法的高精度思想,能计算任意位数的加减乘。
00 · 学习目标
这一课要解决什么?
学完后,你应该能够
- 字符串倒序存成数字数组
- 逐位相加,满十进一
- 乘法错位相加,最后统一进位
01 · 核心概念
高精度运算:数组模拟竖式加法与乘法
高精度运算:数组模拟竖式加法与乘法:Python 的 int 能自动长大,可很多语言(如 C++)的整数最多只有 20 位左右。竞赛大纲要求掌握“用数组模拟竖式”的通用思想——换任何语言都不怕。
GESP Python 5 级 · 高精度运算浮点数比较受二进制表示误差影响,需要按问题选择容差。
02 · 语法与规则
先记住这 3 条,再开始写程序
字符串倒序存成数字数组先准确读出这条写法的结构与作用。
逐位相加,满十进一换一组最小数据,手工推演一次结果。
乘法错位相加,最后统一进位再用边界值或反例确认它的适用条件。
03 · 完整实例
代码、运行结果和解释放在一起看
# 超级大数运算:数组模拟竖式(高精度思想)
def big_add(a, b):
"""两个非负整数(字符串)相加,返回和的字符串"""
x = [int(c) for c in a[::-1]] # 倒过来存:个位在 x[0]
y = [int(c) for c in b[::-1]]
n = max(len(x), len(y))
x = x + [0] * (n - len(x))
y = y + [0] * (n - len(y))
carry = 0
res = []
for i in range(n):
s = x[i] + y[i] + carry
res.append(s % 10)
carry = s // 10
if carry > 0:
res.append(carry)
return "".join(str(d) for d in res[::-1])
def big_mul(a, b):
"""两个非负整数(字符串)相乘,返回积的字符串"""
x = [int(c) for c in a[::-1]]
y = [int(c) for c in b[::-1]]
res = [0] * (len(x) + len(y))
for i in range(len(x)):
for j in range(len(y)):
res[i + j] = res[i + j] + x[i] * y[j] # 错位相加
for i in range(len(res) - 1): # 统一处理进位
res[i + 1] = res[i + 1] + res[i] // 10
res[i] = res[i] % 10
while len(res) > 1 and res[-1] == 0: # 去掉前导零
res.pop()
return "".join(str(d) for d in res[::-1])
a = "12345678901234567890"
b = "98765432109876543210"
print("a + b =", big_add(a, b))
print("a * b =", big_mul(a, b))
print("——————")
fact = "1"
for i in range(1, 31):
fact = big_mul(fact, str(i))
print("30! =", fact)a + b = 111111111011111111100 a * b = 1219326311370217952237463801111263526900 —————— 30! = 265252859812191058636308480000000
Python 的整数天生支持大数,但 C++ 等语言装不下——数组模拟竖式是通用武功。
04 · 逐步理解
每一步只解决一个问题
- 01
为什么要学高精度
Python 的 int 能自动长大,可很多语言(如 C++)的整数最多只有 20 位左右。竞赛大纲要求掌握“用数组模拟竖式”的通用思想——换任何语言都不怕。
- 02
倒序存储的妙处
把字符串倒过来:个位存在下标 0、十位在下标 1。这样进位永远“往后走”,数字变长也只要在数组末尾添一位,比正着存好写一百倍。
x = [int(c) for c in a[::-1]] - 03
竖式加法逐位模拟
和你列竖式一模一样:同一位的两个数字加进位,得数写个位、十位进上去。s % 10 是留下的,s // 10 是进位的——取模和整除又一次联手。
s = x[i] + y[i] + carry res.append(s % 10) carry = s // 10 - 04
位数不齐与最后的进位
两个加数位数不同时,短的用 0 补齐;循环结束后若 carry 还有值,要再补一位(比如 999 + 1 = 1000)。边界想周全,高精度才靠谱。
if carry > 0: res.append(carry) - 05
乘法 = 错位相加
竖式乘法里,x 的第 i 位乘 y 的第 j 位,结果落在第 i + j 位上。先不管进位,全部错位累加;最后从低位到高位统一进位一次,干净利落。
res[i + j] = res[i + j] + x[i] * y[j] - 06
去前导零与特殊边界
0 × 任何数都该得 0。结果数组可能前面(倒序下是末尾)挂着一串 0,输出前要把它们 pop 掉,但至少要留一位——想想为什么。
while len(res) > 1 and res[-1] == 0: res.pop() - 07
用高精度算 30!
30! 是一个 33 位的天文数字。从 "1" 开始连乘 1 到 30,全程用我们自己写的 big_mul——自己的工具算出自己的大数,成就感拉满。
fact = big_mul(fact, str(i))
05 · 练习与检验
自己写出来,才算真正学会
- 1运行加法,看两个 20 位大数相加
- 2手工验算结果的前几位
- 3运行乘法,看积有多少位
- 4用高精度阶乘算出 30!
06 · 完整知识
继续理解定义、规则和适用边界
第一次学习先完成上面的六个步骤;需要查定义、核对规则、分析误区或理解“为什么”时,再展开对应知识章。
Python 基础运算符、表达式与优先级完整区分算术、比较、逻辑、成员、身份和位运算,并用优先级表消除歧义。+
正式定义
表达式求值得到一个值。运算符规定如何组合操作数;当一个表达式含多个运算符时,优先级和结合方向决定求值顺序,括号可以明确改变顺序。
必须掌握
- / 总是得到浮点结果;// 是向负无穷方向取整的整除;% 与 // 满足 a == (a // b) * b + a % b。
- 比较可以链式书写,如 0 <= x < 10;and/or 会短路并返回最后求值的操作数,不一定返回 bool。
- == 比较值是否相等,is 比较是否为同一个对象;判断 None 应写 is None。
- in/not in 做成员测试;对 dict 测试的是键。
- 位运算作用于整数的二进制位;负整数按无限长二进制补码语义理解。
- 复杂表达式即使能靠优先级正确运行,也应使用括号表达意图。
HIGH → LOW
运算符优先级完整速查
同一行通常优先级相同。复杂表达式仍建议加括号表达意图,不把可读性交给记忆。
| 级别 | 运算符/结构 | 含义 | 结合 |
|---|---|---|---|
最高 | (表达式)、[列表]、{字典/集合} | 分组与容器显示 | — |
| x[index]、x[start:stop]、x(...)、x.attr | 下标、切片、调用、属性 | 左到右 |
| await x | 等待表达式 | — |
| ** | 乘方 | 右到左 |
| +x、-x、~x | 正负号、按位取反 | 右到左 |
| *、@、/、//、% | 乘、矩阵乘、除、整除、取模 | 左到右 |
| +、- | 加、减 | 左到右 |
| <<、>> | 移位 | 左到右 |
| & | 按位与 | 左到右 |
| ^ | 按位异或 | 左到右 |
| | | 按位或 | 左到右 |
| in、not in、is、is not、<、<=、>、>=、!=、== | 成员、身份与比较(可链式) | 链式 |
| not x | 逻辑非 | 右到左 |
| and | 逻辑与(短路) | 左到右 |
| or | 逻辑或(短路) | 左到右 |
| x if condition else y | 条件表达式 | 右到左 |
| lambda | 匿名函数 | — |
最低 | := | 赋值表达式 | — |
常见误区
- 把 // 当成简单截断
- 用 is 比较数字或字符串的值
- 忘记 and 的优先级高于 or
- 连续位移、比较和逻辑运算却不加括号
适用边界
- 浮点数比较受二进制表示误差影响,需要按问题选择容差。
- 运算符可由自定义类重载,因此相同符号对不同类型可能有不同语义。
编码与文本字符串、转义、切片与格式化从不可变字符序列到检索、拆分、拼接、格式化和常用判断方法。+
正式定义
str 是不可变的 Unicode 字符序列。下标访问单个字符,切片生成新字符串;任何看似“修改字符串”的方法都会返回新对象。
必须掌握
- 下标从 0 开始,负下标从末尾开始;切片 s[start:stop:step] 不包含 stop,step 不能为 0。
- 转义序列用于在字面量中表示换行、制表、引号、反斜杠或码点;原始字符串仍有末尾反斜杠限制。
- find 找不到返回 -1,index 找不到抛 ValueError;count 统计不重叠出现次数。
- split 把字符串拆成列表,join 用一个字符串连接可迭代对象中的字符串,strip 只删除两端字符。
- f-string 的格式说明可控制宽度、对齐、精度、进制和百分比;格式化不改变原值。
- isalpha/isdigit 等按 Unicode 定义,不只识别英文字母和 ASCII 数字。
常见误区
- 尝试 s[0] = 'A' 原地修改字符串
- 把 strip('ab') 误解为删除完整子串 'ab'
- find 返回 -1 后直接拿去当有效下标
- 把字节长度与字符长度混为一谈
适用边界
- 正则表达式不在低等级字符串必修范围,但复杂模式匹配时应使用 re,而不是堆叠大量 split/find。
- 面向用户的字符计数还可能涉及组合字符和字素簇,len 统计的是 Unicode 码点序列长度。
Python 基础条件、循环与程序流程准确理解 if、for、while、range、break、continue 和循环嵌套,而不是背代码模板。+
正式定义
控制流决定下一条要执行的语句。分支依据布尔条件选择路径;循环在满足规则时重复执行代码块。Python 用冒号和缩进界定代码块。
必须掌握
- if/elif/else 从上到下判断,只执行第一个为真的分支;else 不写条件。
- for 依次取得可迭代对象中的元素;range(start, stop, step) 包含 start、不包含 stop,step 不能为 0。
- while 在每轮开始前检查条件;循环体必须让状态向终止条件推进。
- break 结束最内层循环,continue 跳过本轮剩余语句,循环的 else 仅在没有被 break 终止时执行。
- 嵌套循环的总执行次数通常需要把各层次数相乘;内层 break 不会结束外层循环。
- 边界测试至少覆盖空范围、单个元素、第一项命中、最后一项命中和始终不命中。
常见误区
- range 的右端点多算或少算一次
- while 忘记更新状态造成死循环
- 把两个互斥条件写成两个独立 if
- 误以为 break 会跳出所有嵌套循环
适用边界
- 流程图是算法的表示方法,不是 Python 语法。
- 递归也能表达重复,但有调用开销和递归深度限制,不能无条件代替循环。
算法方法算法、复杂度与解题验证把题意转成输入、状态、规则与输出,用正确性和复杂度共同评价解法。+
正式定义
算法是解决一类问题的有限、明确步骤。正确性说明算法对所有满足前置条件的输入都得到规定结果;时间和空间复杂度描述输入规模增长时资源使用的增长量级。
必须掌握
- 先明确输入规模 n、数据范围、目标和允许误差,再选择数据结构与算法。
- O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ) 表示增长量级,不是精确运行秒数。
- 顺序代码复杂度取较大项,嵌套循环常相乘,二分每步把范围缩小一半。
- 正确性可用循环不变量、数学归纳、交换论证、反证或状态定义来说明。
- 样例只验证少量输入;必须自己设计边界、极端、重复、有序/逆序和无解数据。
- 优化前先得到正确基线并测量瓶颈,不为小数据盲目增加复杂实现。
常见误区
- 只看样例通过就宣称正确
- 不看数据范围使用 O(n²)
- 二分区间开闭混用
- 把 O(n) 当成永远比 O(log n) 慢固定倍数
适用边界
- 复杂度隐藏常数与硬件差异,但仍是比较规模增长的核心工具。
- 考场策略、课程完成度和算法能力是不同证据,任何单项都不能保证考级通过。
完成检查