1.10 从1加到100:一道简单的数学题挑战一下你的大脑

把从 1 加到 100 拆成循环与公式两条数据通路,比较 CPU、内存、复杂度和定宽溢出。

学习目标

  • 能在同一输入上比较循环加法与等差数列公式的 CPU 指令、内存访问和复杂度
  • 能在读取 n、执行乘法和检查结果的不同顺序中定位定宽整数溢出
  • 能用正常、边界和单一故障样本证明算法选择没有把最终偶然正确当成安全

1.10 从1加到100:一道简单的数学题挑战一下你的大脑

本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 1.10 从1加到100:一道简单的数学题挑战一下你的大脑。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。

“从 1 加到 100”看似只是小学算术,却能把算法、数据通路和类型边界放在同一张桌上。循环方案重复读取计数器并更新累加器,复杂度是 O(n);公式方案把工作压缩成 n * (n + 1) / 2,但乘法中间值可能先超过整数位宽。少做循环不代表不需要验证。

求和数据通路:少做循环也要检查中间值循环重复更新累加器,公式压缩迭代,但乘法可能先越过位宽1输入 n类型与范围输入证据2内存读取与写回输入证据3循环O(n) 累加执行证据4公式O(1) 乘除执行证据5检查结果与溢出当前验收点O(1) 描述操作数量,不替你证明输入类型、位宽和中间值安全
专属图示:把“简单数学题”还原为 CPU、内存与范围检查的可观测路径。

三个会让简单求和失真的陷阱

五个目录节点到数据通路证据

CPU和内存

决定“同一个公式”在机器上如何发生。循环每轮至少更新计数器和累加器,可能反复读取内存;公式减少迭代,却增加乘法、除法、类型转换和范围检查。比较时写清楚是否包含输入解析、缓存未命中与日志成本。

从1加到100

的数学结果是 100 × 101 ÷ 2 = 5050。工程实现不能只抄公式:要定义 n 的类型、允许的最大值、是否允许负数,以及遇到无法表示的结果时返回错误、饱和还是升级类型。

热身

先用 n=0、n=1、n=100 三个输入建立基线:循环结束时累加器是前缀和,计数器刚好越过 n,公式与循环结果一致。热身不是降低标准,而是先把 off-by-one 和单位/类型问题暴露出来。

正式出发

时同时跑循环和公式。循环的每轮不变量是 sum = 1 + ... + (i - 1);公式要在乘法前确认中间值可表示。若任一路径无法安全表示,就返回明确的 overflow,而不是截断后继续。

1.10 从1加到100:一道简单的数学题挑战一下你的大脑

这个标题的真正问题是:当大脑把数学等式视为永恒真理时,机器怎样把它拆成 CPU 指令、寄存器/内存访问和带宽有限的整数。1.10 从1加到100:一道简单的数学题挑战一下你的大脑用一份输入比较两条通路,再用边界证据决定哪条实现可以交付。

最小求和合同

type SumResult =
  | { kind: "ok"; value: bigint; operations: number }
  | { kind: "overflow"; stage: "input" | "multiply" | "accumulate" };
 
function sumLoop(n: bigint, limit: bigint): SumResult {
  if (n < 0n) return { kind: "overflow", stage: "input" };
  let sum = 0n;
  for (let i = 1n; i <= n; i += 1n) {
    sum += i;
    if (sum > limit) return { kind: "overflow", stage: "accumulate" };
  }
  return { kind: "ok", value: sum, operations: Number(n) };
}
 
function sumFormula(n: bigint, limit: bigint): SumResult {
  if (n < 0n) return { kind: "overflow", stage: "input" };
  const a = n;
  const b = n + 1n;
  const product = a * b;
  if (product > limit * 2n) return { kind: "overflow", stage: "multiply" };
  return { kind: "ok", value: product / 2n, operations: 1 };
}

合同先选择明确的 bigint 中间值,再把 limit 当成目标机器类型的可表示上限。若真实实现使用定宽整数,必须在每次加法或乘法前做等价检查;不能因为 JavaScript 示例使用宽类型,就假设 C、RISC-V 或数据库中的整数也无限大。

四步复核两条求和通路

分步1 / 4

1. 热身:固定输入与不变量

用 n=0、1、100 写出预期值、计数器终点、累加器范围和允许的错误。检查循环的初始化、条件和自增各一次,先排除边界索引问题。

求和数据通路:少做循环也要检查中间值循环重复更新累加器,公式压缩迭代,但乘法可能先越过位宽1输入 n类型与范围输入证据2内存读取与写回输入证据3循环O(n) 累加执行证据4公式O(1) 乘除执行证据5检查结果与溢出当前验收点O(1) 描述操作数量,不替你证明输入类型、位宽和中间值安全
专属图示:把“简单数学题”还原为 CPU、内存与范围检查的可观测路径。

Lab

两条求和路径实验

只改变一个条件,观察复杂度、阶段值和溢出判定如何变化。

循环与公式都得到 5050

loop: 100 iterations; formula: 100 × 101 ÷ 2; both within limit

判定

accept:保留两条路径的阶段证据

当前样本:基线 n=100;保存输入、阶段值、操作计数、位宽和重放结果。

正常、边界与单一故障证据

求和证据矩阵:看首个不变量破坏正常样本看一致,边界样本看停止,故障样本看轨迹观察项正常边界故障输入n=0/1/100接近上限窄转换循环不变量成立次数很大少一项乘积可表示中间变大先溢出结果路径相同明确拒绝巧合相等最终数字只是结果;输入、阶段值、位宽和不变量才是解释
专属图示:把 5050 放回产生它的输入、通路和整数边界。
样本只改变的变量预期判定必存证据
正常n=0、1、100,类型和终止条件正确两条路径都得出 5050 或相同基线输入、算法、操作计数、阶段值
边界n 接近位宽上限或结果超过 limit在溢出节点停止并返回明确状态中间乘积、累加器、类型和范围检查
单一故障终止条件、窄转换或一条加法被改动首个不变量破坏被捕获,不能静默通过故障位置、前后轨迹、恢复重放

故障诊断:先定位哪条通路先偏离

  1. 输入节点:确认 n 的解析值、符号、单位和类型;n 小于 0、截断或隐式转换先判为输入问题。
  2. 执行节点:循环看计数器与累加器不变量,公式看乘法前后的中间值;对每个节点记录可表示范围。
  3. 结果节点:用独立参考实现或手算值对照,不把同一条有缺陷的公式当作 oracle。
  4. 恢复节点:选择更宽类型、分解乘法、限制输入或返回 overflow,并从同一输入重新跑,确认轨迹真正改变。

如果循环得到 5050 而公式得到错误值,先查乘法中间值;如果两者都错误,先查 n 和终止条件;如果只在大 n 错误,检查位宽和转换。诊断目标是首个偏离,而不是找到一个能让最终数字“看起来对”的补丁。

最小求和证据包与反例

证据包包含输入 n、整数类型和位宽、初始值、循环不变量、公式中间值、CPU/内存事件口径、操作次数、范围检查、最终状态和重放结果。对性能比较还要固定编译选项、平台和是否包含 I/O,否则数字不能互相比较。

反例可以是 16 位实现计算一个结果尚可表示、但 n*(n+1) 已先溢出的输入;也可以是循环条件写成 < n,只在 n=100 的某次后处理里被偶然修正。保留反例的阶段轨迹,修改一个条件后重新运行,证明通过来自不变量而不是巧合。

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

CPU和内存

执行算术与分支的处理器资源,以及保存输入、累加器和轨迹的存储层次。

从1加到100

以 n=100 为基线的前缀和问题,同时要求定义类型和可表示范围。

热身

固定小输入、预期值和循环不变量,提前暴露终止条件与类型错误。

正式出发

在固定合同下运行并记录两条数据通路及其失败证据。

练习

练习

问题 1: 16 位无符号整数执行 n * (n + 1) / 2。为什么“最终答案能放进 16 位”仍不能证明公式安全?

问题 2: 循环和公式在 n=100 都得到 5050,但 CPU/内存成本要怎么公平比较?

问题 3: 一段循环把 i <= n 改成 i < n 后,某个测试仍然通过。你怎样定位并修复?

资料与写作方式声明

本章以码农翻身权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

本页小结

1.10 从1加到100:一道简单的数学题挑战一下你的大脑的关键不是背出 5050,而是能把循环和公式都还原成 CPU、内存、类型和范围检查。完成标准是先做热身固定不变量,再正式比较两条通路,在定宽边界捕获中间溢出,并用阶段轨迹证明结果不是偶然正确。

讨论

评论区加载中…