公式是一份小型规格说明
设想一个应用为搜索结果中的前四项计算分数。一位开发者从零计数,另一位从一开始;一位返回总和,另一位返回平均值。两种实现都能运行,也都产生数字,却回答了不同的问题。判断谁正确之前,我们需要精确说明预期计算的含义。
数学记法帮助我们做这件事:明确涉及的对象、允许的输入,以及输入与结果之间的关系。只有读懂这些承诺,公式才真正有用。仅仅认识符号并不够:求和范围一变,含义就变;函数的定义域一变,函数本身也可能改变。
贯穿本模块的任务很小:给定非负整数 ,求前 个正奇数的和。 时,各项为 ,结果为 ; 时,没有任何项,结果为 。我们将写出公式,把下标翻译为 Python,并区分几次成功运行与针对全部有效输入的主张。
阅读每个表达式时,始终问:这是什么对象?允许哪些值?结果必须表示什么? 今后阅读损失函数、概率模型、矩阵变换或算法成本时,同样的问题仍然适用。本模块建立数学语言;后续模块再发展证明和专门的数学工具。
实验前阅读 Python 入门。如果学校代数知识不够稳固,先做 入门诊断,并使用 可选复习。这些资源用于发现需要练习的内容,不是限制学习的门槛。
数学对象与数系
数学对象是能够描述并推理的事物,例如数、有序数对、集合、函数或数列。哪些运算有意义,取决于对象的种类。两个实数可以相加,但图书目录与温度是不同种类的对象。程序语言提供表示方式,数学描述则赋予表示以含义。
定义规定一个术语的含义。本系列规定自然数包含零:。其他书籍可能用该符号表示 。没有一项约定在所有文本中强制适用;应先声明再使用。特别要求正整数时,我们写 。零是否允许,关系到空输入、初始计数器和无效数量的处理。
假设说明推理或计算适用的环境。“令 ”允许零而排除 。主张是需要检验的陈述,例如“前 个奇数之和等于 ”。它涉及每个允许的 ;给出求和定义并不自动证明这个主张。定理是从明确假设和认可规则出发,已经用证明建立的主张。模块 04 将系统介绍证明方法。
常见数系构成层次关系。整数 包含负的整值数;有理数 包含 ,其中 为整数且 ;实数 还包括 等无理数。小数只是书写方式: 是有理数,而 的无限小数展开不终止也不循环。
| 数系 | 例子 | 常见用途 |
|---|---|---|
| 计数与非负下标 | ||
| 有符号差值与整数位置 | ||
| 精确比值与比例 | ||
| 测量与连续模型 | ||
| 后续旋转与谱方法的拓展 |
属于与包含使用不同符号。 表示一个对象属于一个集合; 表示前一个集合的每个元素也属于后一个集合。为了声明定义域,我们先使用这部分集合语言;模块 03 再深入学习集合运算。
自然数、整数、有理数与实数相互嵌套。标签举出各层新增的例子;框表示包含关系,不表示各集合中数的数量。
是整数、有理数与实数,但按本系列约定不是自然数。 属于上述四个数系。有限小数 等于有理数 ;它是 的近似值,不是该数的另一种精确写法。
记录数量必须是自然数。测得的电压可以建模为实数,但计算机保存的是有限近似。选择实数模型不意味着每个实数都有精确机器表示。
复数写作 ,其中 且 。这里只作预览,不是实验的先修条件。实数对应 的特殊情况。谱方法和傅里叶方法会用到复数;当前计算使用实数与整数下标。
公式出现字母时,要说明其角色,不要仅凭字母外形猜测。变量可以在允许值中变化;参数在某次分析中保持固定;常量在当前环境中取指定值。这些角色取决于上下文。在 中,可以把 视为参数、 视为输入;以后从数据拟合 时,它又成为变化的量。
记录数量保存为数值 4.0 时,数学上的数量是否因此不再是整数?4.5 能作为记录数量吗?
查看答案
在数学上仍是整数,但 Python 的浮点数与整数是不同软件表示。约定可以明确要求整数表示。 不是整数,不能作为该计数任务的有效数量。应区分数学定义域与软件类型政策。
符号 相等 区间与下标
表示两个表达式指向同一个值,具有对称性:若 ,则 。它不表示“继续下一步”或“保存新值”。 表示不相等; 与 比较有序实数。 包含边界 , 排除它。 是两个条件同时成立。
数学方程 没有实数解,因为两边减去 会得到 。Python 的 x = x + 1 则先用旧值计算右侧,再把结果赋给左侧名称;相等比较使用 ==。数学文本也可能用 或“定义 为 3”,把定义与待求解方程分开。这些写法表示不同操作。
运算优先级也影响含义。通常 ,因为先乘方再处理外面的负号; 则把负号纳入底数。分数线将分子和分母各自分组;代码 1/(x-2) 保留了这种结构,而 1/x-2 是另一表达式。翻译公式时,即使默认优先级正确,也可以加括号帮助读者理解。
区间规定一段实数范围。方括号包含端点,圆括号排除端点: 包含两端, 排除两端, 只包含左端。无穷不是能被包含的实数端点,因此正实数范围写为 。区间 不同于五元素集合 :区间还包含 与 。
绝对值表示到零的距离: 时 , 时 。它总是非负,因此 。 表示距离零小于二,等价于 。以后近似误差和容差也会用到这种距离记法。
分数满足 时被接受。代码对应 0 <= s < 1。 与 满足条件; 与 不满足。把 < 1 改成 <= 1 会改变边界处的约定,即使典型数据中从未出现这个边界值。
下标标记分量或项。数列 中, 是标记为二的项,不是 ,也不是 。上标经常表示乘方,但含义仍取决于声明:以后可能用 表示第几次迭代。括号与解释帮助区分这两种含义。
奇数任务使用 ,整数下标为 。当 时,下标为 ,对应项为 。最后下标是三,但项数是四。若从 开始却不改变公式,就会从 开始,描述另一数列。
表示依次代入允许的整数下标,再相加。展开为 。大写希腊字母 sigma 是聚合指令。第 5 节会深入讨论作用范围与嵌套形式;这些知识已经足够跟踪实验 1。
数学求和上下界都包含在范围内;Python 的 range(start, stop) 不包含 stop。零到三使用 range(4),一到四使用 range(1,5)。官方 Python range 教程 解释该约定。
还要区分精确相等与近似。 承认二者存在差异。显示三位小数不能证明底层数值相等。实验中的整数求和使用精确整数运算;浮点计算需要明确容差。模块 29 将解释更复杂情形下如何选择数值比较方式。
把 翻译成 Python range。解释 与 、 与 的区别。
查看答案
使用 range(1,5)。前者标记项或分量,后者将数值平方。区间包含所有大于零且不超过一的实数,包括 而排除零;有限集合则恰好包含零与一。
函数 定义域与复合
函数为其定义域中的每个输入指定唯一输出。 给出定义域 与陪域 。所有输出必须属于 ,但不要求 的每个元素都被产生。实际达到的输出集合叫像:。这一区分关系到反函数是否存在,以及一个候选输出是否可能出现。
公式只是描述函数的一种方法,并不是完整规格。同一表达式配上不同定义域,可以描述不同函数。 在全部实数上定义时,像为 ;定义域为 时,像为 。两者都能选择 为陪域,但陪域中的未达到值不是函数输出。
在定义域 上产生 。声明的陪域还包含未达到的 。每个输入恰有一条输出箭头;陪域有未使用值是允许的。
定义域限制可能来自表达式,也可能来自问题。实数表达式 不允许 ,因为除零未定义;实数平方根要求被开方数非负。计数函数可以排除负输入,即使其代数表达式能机械代入负值。先说明问题本身的限制,再讨论表达式是否能计算。
函数也可以分段定义:
条件选择规则,并且必须为定义域中每个输入提供唯一结果。这里每个实数恰好属于一个分支,边界 也被覆盖。若分支重叠且给出不一致输出,就不是有效函数;若漏掉一部分输入,就没有覆盖声称的定义域。
第一分支给出 ;第二分支给出 与 。零不使用第一分支,因为其条件是严格小于。Python 可先写 if x < 0: return 2*x + 1,再写 return x*x。边界测试属于检查数学规格的一部分。
复合是把一个函数的输出交给另一个函数。 中外层函数写在左边,但先执行 。有效输入必须属于 的定义域,且中间结果必须属于 的定义域。若 的所有输出都满足外层要求,复合在 的全部定义域上成立;否则必须进一步限制允许输入。
令 ,,定义域均为实数。于是
时,第一种先得到 ,再得到 ;第二种先得到 ,再得到 。两者是不同函数。不能仅因输入类型相同,就随意交换复合次序。
两条路径从相同输入出发,使用相同规则,却产生不同输出。沿实际计算顺序阅读箭头,而不是把书写的复合从左往右执行。
若改成 、,外层要求 ,因此复合要求 ,即 或 。内层平方允许零,但整个复合不允许。只检查原输入是否满足内层条件,会漏掉中间值的定义域错误。
反函数唯一地逆转映射。实数函数 的反函数为 ;两个方向的复合都会恢复原值。 表示反映射,不是倒数 。全部实数上的平方不能唯一恢复输入,因为 与 都产生 。将平方的定义域和陪域都限制为 ,才得到反函数 。
程序实现也不一定是仅关于其显式参数的数学函数。读取时钟、可变全局变量或随机生成器的过程,可能对相同显式输入返回不同结果。需要确定性映射时,应把额外状态纳入模型。后续概率模块会直接建模随机输出。
对于 、,给出 的公式与定义域。只检查 接受输入是否足够?
查看答案
复合为 ,定义域是实数 。 对内层有效,却产生外层拒绝的 。原输入与中间输出都要检查定义域。
乘方 对数与增长
正整数指数表示重复相乘,因此 。非零底数的零次方是 ,负整数次方是 。底数限制不可省略:零的负次方会要求除零。 在不同环境中有不同约定;本模块不为它规定普遍的实数解释。
在有效定义域内,乘方满足 与 。正实数底数允许将这些关系自然推广到实数指数。负底数和非整数指数的实数定义域则更复杂,例如 不是实数。不能仅凭符号相似,就把正底数规则套到所有表达式。
指数函数固定底数、改变指数: 的输入是 。幂函数 则改变底数、固定指数。 时二者都给出 ; 时分别为 与 ; 时又都为 。若干重合值并不说明函数相同。实验 2 在明确范围内比较它们;模块 06 再研究更长远的增长。
对数询问什么指数会产生某个正值。实数底数 、 时, 表示 ,要求 。它是指数映射的反函数。自然对数 使用底数 ,约为 的正无理数。本系列未注明底数的数学 默认表示自然对数。
定义域也是恒等式的一部分。对任意实数 ,;对正数 ,。后一表达式在零或负实数上未定义。这种计算器报错反映所规定实函数的定义域,并不是偶然的软件不便。
令 。对数参数必须为正,因此 ,即 。定义域是 ,不是 。,; 时参数为零,实数表达式未定义。
对正实数 ,对数将乘法转成加法:
乘积恒等式来自指数规则:写成 、,则 ,取自然对数得到 。参数与商必须仍然满足定义域要求。对 ,还成立 。以后,这些关系会把独立概率的乘积转成对数似然之和。
加法没有对应的一般恒等式。取 , 为正,但 。一个有效反例足以否定声称对全部正输入成立的恒等式,却不能自动建立另一条公式;乘积规则需要自己的解释。
当 、、 时,
令 ,于是 。取自然对数得到 。由于 ,分母不为零,可以相除。因此 。精确关系由比值表达,而不是任意一个经过舍入的小数表达。
比较增长必须说明输入范围和目的。把 乘以正的常数 会改变数值,却不改变随 增长的总体形态。在编码和信息计算中,底数仍重要,因为单位会改变:二进制对数对应比特,自然对数对应纳特。信息论模块会再次讨论这些单位。
图像只能提供显示窗口内的证据,不能证明全部正输入上的性质。指数函数可能在某段范围内小于多项式,却在更后面增长更快。坐标轴也必须检查:对数坐标会改变数值间的显示间距。实验 2 使用相同的普通坐标轴;曲线离开可见矩形,不意味着函数不再存在。
允许哪些输入? 是否对全部正数 成立?用计算解释。
查看答案
要求 ,因此 。取 ,左侧 ,右侧为 。两侧都在定义域内,这是有效反例。
有限求和 乘积与作用范围
有限求和在声明的下标范围内聚合各项。求和符号绑定其中的下标: 中, 逐一取一到 的整数值,而 仍是控制范围的参数。绑定范围之外的字母可能有另一含义。这与程序循环变量的作用范围相似。
可靠地阅读求和,需要确定四部分:下标名称、起点、终点和被计算的项。先展开小例子,再尝试化简:
共有四项。不能把下标或最后一项简单乘以上界作为捷径。改成 ,等于把末项错误地使用四次。
下标是绑定变量,也称哑变量。完整改名不改变总和:。但改名不能捕获原有变量。在 中, 是自由变量, 被绑定。若把绑定下标改成 ,却不保留原来的自由输入,就悄悄改变了表达式。应选择不同名称,类似避免覆盖函数参数。
有限求和可以使用普通加法规则重新排列和分配。常数 满足
展开两侧,就能看到每项恰好出现一次。这是当前加法与乘法的性质,并不证明所有函数满足 。无限求和重新排列时还需要收敛条件;这里只讨论有限情形。
空和为零:没有贡献时,已有累计值不变。奇数任务中, 使零到 的范围为空,不是需要计算一个负下标末项。结果来自聚合约定,不是把不存在的项假装为零。
有限乘积使用 ,例如 。空积为一,因为没有因子时,已有乘积不变。若空积为零,任何普通乘积拆成非空部分与空部分后都会消失。这些单位元解释了 sum([]) 与 math.prod([])。
从零开始:;从一开始必须改项公式:。转换关系是 。若只改起点、不改原项,得到 ,对应另一任务。
双重求和包含两个聚合指令:
先固定外层 ,完成内层 的求和,再把内层结果相加。 时,各项 合计 ; 时,各项 合计 ;整体为 。两个外层值各对应三个内层值,因此共有六项。
矩形下标网格显示全部六项。每行对应一个外层下标;相加各行总和就是外层求和。这是下标示意图,不要求先学矩阵代数。
因为是有限矩形,先加列也相同:。非矩形范围需要更谨慎。在 中,内层上界依赖 。各行分别为 、、,总和 。不转换有效下标对而直接交换两个求和符号,会改变范围。
求和后仍保留的变量决定输出含义。 中绑定下标消失,但输入 保留;展开得到 。 中, 仍是自由下标,用来标记哪一列的总和。模块 09 使用数组时,这一区分能避免形状与轴的错误。
矩形双重求和使用外层 range(1,3) 与内层 range(1,4)。三角形例子使用外层 range(1,4) 和内层 range(1,i+1)。扩大输入之前,检查极小例子与空范围。一个很大的、看起来合理的结果,不能显示究竟遗漏了哪些下标。
展开 。哪些符号被绑定? 中哪个变量仍然自由?
查看答案
各行是 、、,总和 。双重求和绑定 与 。第二个表达式只绑定 , 自由;展开为 。
约定 主张与代码翻译
数学约定说明允许的输入与结果必须满足的关系。奇数任务输入为非负整数 ,输出为整数总和
其中空和为零,输入不被修改。负数量、非整数量,以及要求平均值而非总和,都不属于该规格。软件政策另行说明无效输入如何处理;实验抛出清晰的 ValueError。
逐项翻译:数学数量成为 Python 整数;包含 的范围成为 range(n);项写为 2 * i + 1,代码需要乘法运算符;累计值从零开始,每次加入一项。这比把符号机械复制到解释方式不同的语言更可靠。
时,下标依次为 ,项为 ,累计值为 。 时没有迭代,初始值 已是正确输出; 时,只加入零下标的项,得到 。
这种跟踪检查经常暴露起止位置错误的边界情形,却不是对全部非负数量的正确性证明。以后将用不变式完成一般论证。
的定义与 的主张角色不同。定义说明计算内容,恒等式声称另一个表达式对全部有效 给出相同值。 的检查只确认一个案例。一般恒等式可用模块 04 的归纳法,或本模块可选练习中的有限求和代数论证建立。
否定全称主张常常比证明更容易。否定“每个实函数都有加性”,可取 、、:,而 。所有输入和中间值都有效,因此反例成立。若把未定义的对数作为“反例”,就不能检验只对正参数声称的恒等式。
区分三种故障。规格故障是公式描述了错误任务,如要求总和却写成平均值。翻译故障是代码实现了错误表达式,如该用 range(n) 却写 range(1,n)。数值故障是机器算术没有足够好地近似预期计算,如指数溢出或相等检查不合适。不同故障需要不同修复。
AI 论文写 时,同样的基本技能仍适用: 自由、 绑定; 必须为正,因为平均值不能除以零;求和改成平均会改变尺度。你暂时不需要会最小化它,也能读懂所声明的关系;后续微分与优化再提供进一步工具。
不要默默给公式增加情况。若将来要求 返回“无分数”而非零,应修改约定与检查;若函数输入从标量改成数组,应声明新的输入输出形状。数学精确性让这些选择清晰可见,便于讨论与验证。
sum(2*i+1 for i in range(1,n)) 为什么不符合约定?给出揭示错误的最小正输入。
查看答案
它遗漏下标零,其项为 。 时,错误范围为空,返回 ,约定却要求 。 时两种代码碰巧都返回零,所以只检查空情况会漏掉故障。
常见误解
| 现象 | 原因 | 修复 |
|---|---|---|
| “数量为 4,所以零起始末下标为 4” | 混淆数量与标签 | 写出下标 0,1,2,3 |
| 内层有效,外层却失败 | 只检查原输入 | 检查中间值是否属于外层定义域 |
| 假定每个陪域值都能达到 | 混淆陪域与像 | 求出实际达到值或描述像 |
| 几个例子正确便接受主张 | 混淆证据与证明 | 明确量词,寻找论证或有效反例 |
| 对数被分配到加法上 | 把乘积规则用于另一运算 | 使用 1,1 等正输入比较两式 |
| “曲线在这里结束” | 混淆显示范围与定义域 | 说明定义域,检查是否离开坐标范围 |
| 把浮点结果当成精确值 | 忽略表示与舍入 | 标注近似,使用有理由的容差 |
准备实验
若脚本中有不熟悉的语法,先读 Python 入门。把各脚本下载到能找到的文件夹,在该文件夹打开终端,运行 python lab1_sums.py、python lab2_functions.py 或 python lab3_diagnose.py。Windows 可能需要 py,macOS/Linux 常用 python3;检查所选解释器为 Python 3.11 或更新版本。脚本只使用标准库。
下面提供完整脚本与网页构建器实际捕获的输出。执行之前先阅读问题,预测主要数值。本地改变脚本会改变输出;发布的输出对应未修改版本。实验 2 生成的图像可以直接用浏览器打开。
实验 1 展开并跟踪有限求和
目标: 将整数范围与项及其总和联系起来。先预测 的项和总和,再运行代码。
"""Read finite sums and products. Run with Python 3.11+; no packages needed."""
from math import prod
def odd_sum(n):
"""Sum the first n positive odd integers; n must be a nonnegative int."""
if isinstance(n, bool) or not isinstance(n, int) or n < 0:
raise ValueError("n must be a nonnegative integer")
terms = [2 * i + 1 for i in range(n)]
return terms, sum(terms)
def rectangular_sum(rows, columns):
"""Sum 10*i+j for mathematical indices 1..rows and 1..columns."""
return [[10 * i + j for j in range(1, columns + 1)]
for i in range(1, rows + 1)]
if __name__ == "__main__":
for n in (0, 1, 4, 6):
terms, total = odd_sum(n)
assert total == n * n
print(f"n={n}: terms={terms}, sum={total}, n*n={n*n}")
grid = rectangular_sum(2, 3)
total = sum(sum(row) for row in grid)
assert total == 102
print(f"grid={grid}, double_sum={total}")
assert sum([]) == 0 and prod([]) == 1
print(f"empty_sum={sum([])}, empty_product={prod([])}")
print("Checks passed on these inputs; examples alone are not a general proof.")
n=0: terms=[], sum=0, n*n=0
n=1: terms=[1], sum=1, n*n=1
n=4: terms=[1, 3, 5, 7], sum=16, n*n=16
n=6: terms=[1, 3, 5, 7, 9, 11], sum=36, n*n=36
grid=[[11, 12, 13], [21, 22, 23]], double_sum=102
empty_sum=0, empty_product=1
Checks passed on these inputs; examples alone are not a general proof.
探索: 改为 ,预测结果。解释二维表为何有六项且总和为 。检查空积,并解释为什么一是相应单位元。
书面结果: 展示一次完整展开与跟踪,明确断言检查了哪些输入,解释为何这些检查没有证明所有 上的恒等式。
实验 2 比较函数与定义域
目标: 联系数值、公式与图像。先预测 的一行,以及 时两种复合。对数使用实数定义域 。
"""Compare four functions and write an SVG plot using only the standard library.
Run: python lab2_functions.py
Optional: python lab2_functions.py --output functions.svg
"""
import argparse
from html import escape
import math
from pathlib import Path
def logarithm(x):
if x <= 0:
raise ValueError("ln(x) requires x > 0")
return math.log(x)
FUNCTIONS = [("2*x+1", lambda x: 2 * x + 1, "#2563eb"),
("x*x", lambda x: x * x, "#7e22ce"),
("2**x", lambda x: 2 ** x, "#15803d"),
("ln(x)", logarithm, "#c2410c")]
def make_plot(destination):
width, height = 760, 420
left, right, top, bottom = 60, 720, 45, 335
x_min, x_max, y_min, y_max = -2, 4, -4, 18
def px(x):
return left + (x - x_min) / (x_max - x_min) * (right - left)
def py(y):
return bottom - (y - y_min) / (y_max - y_min) * (bottom - top)
parts = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}" role="img" aria-labelledby="title desc">',
'<title id="title">Four functions over stated domains</title>',
'<desc id="desc">Linear, square, exponential and natural logarithm. The logarithm has no curve at nonpositive inputs. All curves share the same axes.</desc>',
'<rect width="760" height="420" fill="white"/>',
'<g font-family="sans-serif" font-size="13" fill="#475569">']
for x in range(-2, 5):
parts.append(f'<path d="M{px(x):.2f},{top} V{bottom}" stroke="#e2e8f0"/>')
parts.append(f'<text x="{px(x):.2f}" y="355" text-anchor="middle">{x}</text>')
for y in range(-4, 19, 2):
parts.append(f'<path d="M{left},{py(y):.2f} H{right}" stroke="#e2e8f0"/>')
parts.append(f'<text x="48" y="{py(y)+4:.2f}" text-anchor="end">{y}</text>')
parts.extend([f'<path d="M{left},{py(0):.2f} H{right} M{px(0):.2f},{top} V{bottom}" stroke="#475569"/>',
'<text x="735" y="355">x</text><text x="35" y="30">y</text></g>'])
for index, (label, function, colour) in enumerate(FUNCTIONS):
commands = []
connected = False
for step in range(601):
x = x_min + step * (x_max - x_min) / 600
try:
y = function(x)
except ValueError:
connected = False
continue
if not y_min <= y <= y_max:
connected = False
continue
commands.append(f'{"L" if connected else "M"}{px(x):.2f},{py(y):.2f}')
connected = True
parts.append(f'<path d="{" ".join(commands)}" fill="none" stroke="{colour}" stroke-width="2.5"/>')
legend_x = 70 + index * 170
parts.append(f'<path d="M{legend_x},387 h20" stroke="{colour}" stroke-width="3"/>')
parts.append(f'<text x="{legend_x+27}" y="391" font-family="monospace" font-size="14">{escape(label)}</text>')
parts.append('</svg>')
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text('\n'.join(parts), encoding='utf-8')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--output', type=Path, default=Path('functions.svg'))
args = parser.parse_args()
print('x 2*x+1 x*x 2**x ln(x)')
for x in (-2, -1, 0, 1, 2, 4):
values = []
for _, function, _ in FUNCTIONS:
try:
values.append(f'{function(x):9.4f}')
except ValueError:
values.append('undefined')
print(f'{x:2d} ' + ' '.join(values))
f = lambda x: 2 * x + 1
g = lambda x: x * x
assert f(g(3)) == 19 and g(f(3)) == 49
print(f'f(g(3))={f(g(3))}; g(f(3))={g(f(3))}')
assert math.isclose(logarithm(4) / logarithm(2), 2.0)
try:
logarithm(0)
except ValueError as error:
print(f'domain check: {error}')
else:
raise AssertionError('zero must be rejected')
make_plot(args.output)
print(f'Plot written: {args.output.name}')
x 2*x+1 x*x 2**x ln(x)
-2 -3.0000 4.0000 0.2500 undefined
-1 -1.0000 1.0000 0.5000 undefined
0 1.0000 0.0000 1.0000 undefined
1 3.0000 1.0000 2.0000 0.0000
2 5.0000 4.0000 4.0000 0.6931
4 9.0000 16.0000 16.0000 1.3863
f(g(3))=19; g(f(3))=49
domain check: ln(x) requires x > 0
Plot written: functions.svg
探索: 打开 functions.svg,找到各条曲线并检查对数从哪里出现。把表格输入改成 0.5,1,3;这样修改时,应同时把整数输入标签改为浮点格式。解释 时对数未定义,与有效曲线超出纵轴上限的区别。
书面结果: 写出得到 与 的中间值,描述四个函数的定义域,解释为何 上的图像不足以证明最终增长性质。
实验 3 诊断错误翻译与恒等式
目标: 在修复之前用有效反例定位错误。先预测少一项的总和,以及平方函数的反例。
"""Find counterexamples and repair a contract, an index range, and an identity."""
import math
def bad_odd_sum(n):
return sum(2 * i + 1 for i in range(1, n))
def odd_sum(n):
if isinstance(n, bool) or not isinstance(n, int) or n < 0:
raise ValueError('n must be a nonnegative integer')
return sum(2 * i + 1 for i in range(n))
def shifted_log(x):
if x <= 1:
raise ValueError('ln(x-1) requires x > 1')
return math.log(x - 1)
if __name__ == '__main__':
n = 4
print(f'off-by-one: n={n}, faulty={bad_odd_sum(n)}, repaired={odd_sum(n)}, expected={n*n}')
assert bad_odd_sum(n) != n * n and odd_sum(n) == n * n
f = lambda x: x * x
a, b = 2, 3
print(f'non-additive square: f(a+b)={f(a+b)}, f(a)+f(b)={f(a)+f(b)}')
assert f(a + b) != f(a) + f(b)
lhs, rhs = math.log(a + b), math.log(a) + math.log(b)
print(f'false log identity: ln(a+b)={lhs:.6f}, ln(a)+ln(b)={rhs:.6f}')
assert not math.isclose(lhs, rhs)
assert math.isclose(math.log(a * b), rhs)
print(f'correct product identity: ln(a*b)={math.log(a*b):.6f}')
for x in (1, 2):
try:
print(f'shifted_log({x})={shifted_log(x):.6f}')
except ValueError as error:
print(f'shifted_log({x}) rejected: {error}')
for invalid in (-1, 2.5, True):
try:
odd_sum(invalid)
except ValueError:
print(f'odd_sum({invalid!r}) rejected')
else:
raise AssertionError('invalid input accepted')
print('Repairs preserve the stated domains; counterexamples refute the false claims.')
off-by-one: n=4, faulty=15, repaired=16, expected=16
non-additive square: f(a+b)=25, f(a)+f(b)=13
false log identity: ln(a+b)=1.609438, ln(a)+ln(b)=1.791759
correct product identity: ln(a*b)=1.791759
shifted_log(1) rejected: ln(x-1) requires x > 1
shifted_log(2)=0.000000
odd_sum(-1) rejected
odd_sum(2.5) rejected
odd_sum(True) rejected
Repairs preserve the stated domains; counterexamples refute the false claims.
探索: 用 检查最小正边界;为对数比较选择其他正数 ;用 shifted_log(0) 和 shifted_log(3) 检查定义域边界两侧。不要为了让程序“成功”而关闭定义域检查。
书面结果: 区分下标范围错误、错误数学恒等式与无效输入,按原约定解释各修复。尽管 Python 将布尔值视为整数子类,这里仍明确排除它们作为数量。
练习与完整解答
练习 1–12 为必做,约 80 分钟;13–14 为可选拓展,额外约 25 分钟。先写自己的解答,再展开答案。
将 分类到 。一个数能属于多个数系吗?
查看解答
属于四者; 属于 ; 属于 ; 属于 ,是无理数。数系嵌套,所以成员资格可以重叠。本系列自然数包含零。
计算 、、。解释 Python 赋值 x = x + 1,不要把它当作实数方程。
查看解答
结果为 。第一式的负号在平方外面。赋值先读取右侧旧值,加一,再把结果关联到 x;它不声称旧值等于新值。
中哪些属于 ?把一到四的整数下标翻译为 Python 范围。
查看解答
与 属于区间;圆括号排除零,二超出上界。使用 range(1,5),因为终点不包含。区间还包括未列出的其他实数。
展开 与 。给出相应空范围的聚合值。
查看解答
和为 ;积为 。空和为零,空积为一。没有项时,不要代入无效末下标。
给定 、,推导 与 ,并在 计算两者。
查看解答
,取值为 。另一顺序为 ,在 得到 。两者均在实数上定义,但不是同一函数。
展开并化简 。指出自由与绑定变量,并安全改名绑定变量。
查看解答
展开得到 。 自由, 被求和绑定。 保留含义; 会捕获原输入并改变含义。
推导实数函数 的反函数,验证两个复合方向。解释平方为什么需要限制定义域才能有反函数。
查看解答
由 解得 。因此 ,。实数平方把 、 都映到 ,不能唯一恢复输入。将其定义域与陪域限制为非负实数后,反函数为平方根。
按行与按列计算 。解释为什么不能直接对 使用相同的交换论证。
查看解答
行总和为 、,整体 ;列总和为 ,整体仍为 。有限矩形的六个下标对在两种次序中各出现一次。三角形范围的有效对依赖外层下标,其总和为 ;交换时必须保留同一三角形下标集合。
为实数标量函数写约定与代码: 时 ,否则为 。给出三个检查,包括边界,并描述输出可能的符号。
查看解答
约定:输入输出各为一个实数标量,不修改输入。实现:
def h(x):
if x < 0:
return 2 * x + 1
return x * x
检查 、、。负输入分支可以产生负值、零或正值: 时分别输出 。另一分支产生非负值。输入符号不能单独确定第一分支结果的符号。
令 、。给出复合定义域,在 计算。如果陪域声明为 ,每个实输出都能达到吗?
查看解答
要求 ,定义域为 。 与 输出 ,零处未定义。任意实数 都可达到:取 ,则 。此例像等于陪域,但这是需要论证的性质,并非所有函数都如此。
开发者用 range(1,n) 计算前 个正奇数之和。找最小正反例、修复,并说明无效输入处理。
查看解答
时,范围为空,错误返回零而非一。使用 range(n) 配项 2*i+1,或 range(1,n+1) 配项 2*i-1。要求非负整数,清楚拒绝负数与非整数;若排除布尔值,必须明确检查,因为 Python 的 bool 是 int 子类。修复后检查零、一及较大数量。
诊断“每个函数都分配到加法上”与“正输入满足 ”。提供有效反例,解释为什么成功检查不能证明全称主张。
查看解答
第一条取 、、,得到 。第二条取 ,得到 。所有表达式均在有效定义域内。一个矛盾输入足以否定全称陈述;有限成功案例仍留下其他输入未检查。正确对数恒等式涉及乘积而非和。
可选:利用逆序有限求和推导 ,包括 。
查看解答
时,令 ,逆序后逐项相加。每个配对项为 ,共有 个,因此 。于是 。 时,两侧都为零。该论证覆盖整个定义域,而非仅观察到的例子。
可选:正确交换 的求和顺序,给出新界,并验证 。
查看解答
有效下标对满足 。先固定 后, 从 到 ,因此等价表达式为 。 时为 。下标对的描述解释了边界为什么改变。
自测题
九道选择题自动检查。第 10 题需要解释,请与参考答案比较并自评。自动得分只涉及九道选择题。书面回答与复核标记会和时段进度一样保存在当前浏览器。
查看答案
对于 、,每个实数 都是有效内层输入,但 产生 ,不属于外层定义域。复合为 ,定义域为 。只有明确解释中间失败与结果定义域,才给书面题这一分;其他正确例子也可。
引导阅读
必读,10 分钟: 使用 MIT 计算机科学数学 的函数材料。阅读其链接教材中函数、定义域与陪域的定义,暂不继续关系与证明内容。写一个陪域有未使用值的映射,解释为什么仍是函数。
必读,15 分钟: 配合下方记法总结,阅读 机器学习数学 的数学语言与记法介绍。找一个有限带下标表达式,说明各符号角色,并展开三项实例。这里只预览书中记法,不要求已经理解后续线性代数。
可选: 比较代码与记法时,阅读官方 Python range 例子 与 对数文档。涉及行为差异时,使用匹配本地解释器版本的文档。本模块数学说明与练习为原创,外部资源供进一步阅读。
复习与逻辑模块准备
思想链条是:声明对象与允许值,明确运算,检查定义域,展开小例子,翻译作用范围与下标,再用有效论证检验主张。函数的像是实际达到的输出,陪域是声明目标集合。有限求和绑定其下标,其他输入可能仍然自由。
结业任务: 为练习 9 的分段函数写规格与 Python 实现,展开一个小双重求和,并在有效输入上否定对数加法恒等式。不要只复述答案,应能自己解释。课程建议评分为书面练习 60 分、实验解释 30 分、自测 10 分,其中包括复核后的书面回答。
模块 02 精确说明“每个”“存在”“如果”“只有当”等表达。开始前应能解释定义与关于全部输入的主张之间的区别。保留本模块反例,它是逻辑推理的起点。
记法与双语术语
| 符号或英文术语 | 含义 | 中文 |
|---|---|---|
| Domain | 允许输入 | 定义域 |
| Codomain | 声明目标集合 | 陪域 |
| Image | 实际达到的输出 | 像 |
| Composition | 先 g 后 f | 复合函数 |
| Inverse | 唯一逆转映射 | 反函数 |
| / | 成员资格 / 集合包含 | 属于 / 包含于 |
| 包含 a,排除 b | 左闭右开区间 | |
| / | 和 / 积 | 求和 / 乘积 |
| Bound / free variable | 受作用域限制的下标 / 保留输入 | 绑定变量 / 自由变量 |
| Definition / assumption / claim | 含义 / 环境 / 待建立陈述 | 定义 / 假设 / 主张 |
| Counterexample | 否定全称主张的有效案例 | 反例 |
| Contract | 允许输入与保证结果 | 约定 |