十四个开关 · 被打回的记录三十七处2026-09-07

被打回的记录

十四份演示网上都有替代品(没这么较真,但有)。「我预期错了,这是被打回的记录」——没有。 这一页把每一处订正摊开:我以为什么、实测是什么、为什么会错,以及是哪一道关抓到的

⇒ ★★★ 最该带走的一条在最前面说:三十七处里没有一处是「算法本身出乎意料」—— 出乎意料的全都是我对它的描述。算法一直在那儿,打折的是那些流传很广的说法: 2m~3m、√机器精度、4096 倍、蒙特卡洛是水平线。

20 A 类 · 结论错了

动手前拍的定性判断,被真数打翻。错得显眼——数字和预期差着量级,一眼看得出不对。

靠什么抓:先写下预期,再实测。不写下来就没有被打回这回事。

11 B 类 · 测量本身不干净

⚠ 更危险:它给出的是一个看起来完全正常的错数字。量级对、单位对、趋势也对,就是错的。

靠什么抓:同一件事用两份独立实现各算一遍 —— 十一处里有八处是这么暴露的。剩下三处靠的是换个问法重量一遍

6 C 类 · 检查在空转

断言写得像在验,实际什么都没看。⚠ 比 B 类还隐蔽——B 类至少给一个错数字,这一类连看都没看

靠什么抓:把手抄的数换成槽位,由实现在运行时填,闸门按键→值核对。

A 类 · 20 处

结论错了

这一类的价值在于:订正之后的结论都比原来的强,因为它背后有个数。 「FFT 比 DFT 快很多」谁都会说;「n = 2048 时次数比 372 倍、秒表比只有 109 倍,差的那部分是位反转和循环开销」才是一句有内容的话。

01傅里叶
我以为二维图像上 FFT 比 DFT 快 4096 倍
实测是64 倍。两层都错了。

拿不可分离的 N⁴ 双重和去比是 8192 倍,可那种实现没人会写; 两边都用可分离算法(先按行再按列)才是公平的对照。比较的前提没对齐,比出来的倍数就是编的。

01傅里叶
我以为小 n 时 DFT 反而更快,交叉点在 n = 16~64。
实测是没有交叉点,FFT 从 n = 4 就赢。

这条「常识」来自教科书里对递归实现的讨论。这里的实现是迭代的、旋转因子预先算好, 簿记开销远没有那么大。「小规模时朴素算法更快」是对的一般原理,但它的交叉点取决于具体实现,不能照抄。

02纠错码
我以为崩点在 255q = 16,也就是 6.3%
实测是整幅图的崩点左移到 3.6%

6.3% 是单个码字的 50% 分界;而一幅图要 74 个码字全部成功。 要每个的失败率低于 1/74,得让 255q + 2.2√(255q) < 16。 ★ 可靠性按最弱一环算,不按平均。这一条在任何「N 个部件都得好」的系统上都成立。

04压缩
我以为三层加起来压缩比 100~400 倍
实测是33.7 倍(q 拉到 16 才到 105 倍,代价是画质掉到 28.6 dB)。

400 倍是 H.264 的数字,它背后有亚像素运动补偿、帧内预测、可变块大小、CABAC。 这份演示里的是个玩具编解码器,拿工业级的数字给玩具贴金就是在骗人——页面里如实写了差在哪。

04压缩
我以为运动补偿是最值钱的一层。
实测是只值 1.02 倍;最值钱的是熵编码(19.6 倍)。

这一处最有意思,因为它顺手把病根也量出来了:镜头静止时运动补偿值 1.91 倍, 整数摇镜值 1.39 倍,非整数摇镜只剩 1.01 倍——块匹配只能整像素对齐,摄影机不会按整像素平移。 ⇒ 亚像素运动补偿不是锦上添花,是这一层能不能成立的前提。

05卡尔曼
我以为真值落在 2σ 误差椭圆内应该是 95%
实测是98%(二维高斯的正确数),而且实测 100%——这个指标没有区分度

95% 是一维正态的 2σ 覆盖率,二维是 1 − e⁻² = 86.5%(马氏距离), 按椭圆半径 2σ 算是 98%。更要紧的是第二句:一个永远显示 100% 的指标, 关掉开关它也是 100%,它证明不了任何事,换成了 RMSE ÷ 自报 σ。

05卡尔曼
我以为验收线定在「120 秒漂出 500 米」。
实测是538 米——余量只剩 7.6%,改成 400 米。

验收标准卡在实测值边上 7% 的位置,等于把演示的成败押在随机种子上。 阈值要留够余量,否则绿的那一次只是运气。现在的阈值 400 米,实测在 104 秒就越过了。

06维数灾难
我以为蒙特卡洛的采样数是一条水平线(和维度无关)。
实测是下降

「和维度无关」说的是收敛率 1/√N,不是采样数本身。采样数还要看被积函数的方差 σ, 而 σ 随维度略微下降(测度集中:高维里函数值挤向均值)。 ⇒ 结论从「不受维度影响」变成了「不但不受影响,还略占便宜」——更强,而且背后有个数

06维数灾难
我以为精度要求越严,网格法输给蒙特卡洛的交叉点越靠左。
实测是5% / 1% / 0.1% 三档全是 d = 4

因为交叉维度 = 2 × 求积阶数,和精度要求无关:网格的误差是 h^p、点数是 h⁻ᵈ, 蒙特卡洛是 N^(−1/2),两边解出来 ε 正好约掉。一个我以为是经验值的东西,其实是个恒等式。

07梯度
我以为数值微分的最优 ε ≈ √机器精度 ≈ 1.5×10⁻⁸。
实测是底在 10⁻⁴ ~ 10⁻⁵,因为这里用的是中心差分:∛机器精度。

√εₘ 是前向差分的答案(截断误差 O(ε));中心差分的截断误差是 O(ε²), 平衡点挪到 ∛εₘ ≈ 6×10⁻⁶。这是流传最广的一条数值计算口诀,而它只对一半的情况成立。 实测最低点还比理论点大一档半——这个网络的三阶导很小。

08拥塞
我以为关掉 AIMD,吞吐会塌一个数量级。
实测是吞吐一点没变(还是 100%),塌的是公平性(0.997 → 0.461)。

这一处直接改掉了整份演示的立论。链路照样满载——拥塞控制不是在抢救总吞吐, 是在决定这些吞吐怎么分。而真正塌吞吐的是重传定时器(100% → 6.2%)。 ⇒ 「关掉它互联网就崩了」这句话,得说清楚崩的是哪一样。

08拥塞
我以为两个开关要凑在一起才要命。
实测是它们各管各的:RTO 管吞吐,AIMD 管公平,互不重叠。

本来打算做成「单独关都还行,一起关才崩」的交互效应。实测下来是两条正交的轴。 正交比耦合更好讲——一个开关一句话,不用讲组合。

09单纯形
我以为单纯形访问的顶点数约 2m ~ 3m(流传很广的经验值)。
实测是0.38m ~ 0.57m,小四到八倍。

⚠ 这一条订正后来自己也被打回了一次(见 C 类):最初那七个数是手抄的。 现在两边跑同一把写死的标准扫描。站得住的是「线性于 m」这个形状,那个常数取决于你喂它什么 LP—— 教科书那个 2m~3m 说的是真实世界的问题,比随机造出来的难得多。

10一致性哈希
我以为N = 8 加到 9 台,取模要搬 87.5% 的键。
实测是89.0%

闭式解是 N/(N+1),不是 (N−1)/N——我把 N 当成了旧的机器台数。 算的时候用 8 台,写下来时又按 9 台套公式,两头对不上。 这一处一点也不可疑:87.5% 和 88.9% 差不到两个百分点,结论一个字都不用改, 所以它能一直待在那儿

10一致性哈希
我以为1/(N+1) 是搬家比例的理论下界——新机器总得拿到它那一份。
实测是不是下界,是期望。N = 16 时实测 5.7%,低于 1/17 = 5.9%。

新机器接管多少弧本身就是随机的,可以比均值少。 ⇒ 真正站得住的是另一句,而且比「下界」这个说法更强搬家的键恒等于新机器接管的键,一个都不多(逐键数过,搬去别处的是 0 个)。 ★ 下界只给一个不等号,恒等式能逐键验。把一句猜的不等式换成一句能验的等式,这是净赚。

10一致性哈希
我以为取模的负载几乎完美,变异系数 < 1%
实测是√(N/K)——随机器台数和键数变。N = 64、K = 65 536 时是 3.65%

「几乎完美」是个没有量纲的说法。取模把键均匀撒进 N 个桶, 每桶的波动是多项式分布的 √(K/N),除以均值就是 √(N/K)机器越多、键越少,它越不完美——而这恰恰是虚拟节点要对付的那个方向。

11拍卖与匹配
我以为关掉二价,收入和配置效率一起掉
实测是两样都没掉:收入 0.66640.6667,效率都是 100.0%

收入没掉是收入等价定理;效率没掉是因为所有人同比例压价,压价保序,最高估值的照样赢。 ★ 这一处把整份演示的论点换掉了:二价买的不是钱,是「不用猜」—— 二价的最优出价永远是 1.000 × v, 一价从 0.484 × v 爬到 0.947 × v对手多一个你就得重算一次。 ⇒ 和 08「关掉 AIMD 吞吐一点没变,塌的是公平」是同一种形状。

11拍卖与匹配
我以为一价的毛病是出价会互相试探、来回震荡
实测是对称均衡下它不震荡——它只是把答案挪到了你算不出来的地方。

震荡是学习过程的现象,不是机制本身的性质。机制本身的性质是那个不对称的代价: 二价里拿一价的习惯压价,亏 26.5%; 一价里照实出价,收益恰好是 0,亏 100%。 ⇒ 把「动态上会怎样」和「均衡是什么」分开说,否则量到的是自己那个学习器的脾气。

13FM-index
我以为BWT 索引比原文还小——这是它最出名的那句话。
实测是随机序列上几乎压不动:游程数 r / n = 0.748

把重复元件覆盖拉到 4×,才掉到 0.190。 ⇒ 压缩来自重复,不来自变换本身。BWT 做的事是把「上下文相同的字符」聚到一起—— 如果根本没有重复的上下文,它没什么可聚的。 真实基因组一半以上是重复序列,FM-index 的便宜是「基因组恰好长这样」换来的, 不是这个数据结构凭空变出来的。

14PageRank
我以为PageRank 让链接农场失效
实测是它只是变贵了:农场拉到 500 个,被推的那页在 PageRank 下也进了前十 (第 10 名),前十里还有 1 个是农场页本身。

挡不住的原因写在公式里:每个页面都有 (1−d)/N基础分——随机冲浪者会凭空跳过来。 农场页再没用也有这一份,造得够多就能凑出来。 ★ ⇒ 「抗操纵」不是一个是非题,是一个价目表:把目标页推进前十,数入链要 36 个垃圾页,PageRank 要 340 个—— 贵 9 倍,而不是「不可能」。 能量出价格,比声称免疫有用得多。

B 类 · 11 处

测量本身不干净

这一类给出的是一个看起来完全正常的错数字:量级对、单位对、趋势也对。 A 类靠「先写预期」抓得到,因为它离谱;B 类离谱不了——它就长在你预期的那个范围里。 十一处里有八处是「页面自己的数和独立核验对不上」或者「一个本该成立的等式不成立」才暴露的;剩下三处更麻烦——它们的数字自洽,只是在回答另一个问题,只能靠换个问法重量一遍。

01傅里叶
量出来秒表比 1194 倍
真相是109 倍

第一版 DFT 的内层在调 Math.cos,而 FFT 用的是预先算好的旋转因子表。 量的不是「两个算法差多少」,是「有没有查表」。 ⚠ 1194 这个数一点也不可疑——它跟「DFT 慢得多」的预期完全吻合,所以差点就留在页面上了。

02纠错码
量出来Reed–Solomon 译码成功率 0%
真相是译码器两处一起错:Chien 搜索用了数组下标 k 而非次数 N−1−k,Forney 又漏乘 X_j。

只改对一处,成功率仍然是 0%——这次运气好,错得彻底反倒好查。 ⚠ 真正危险的是改对一处就从 0% 跳到 60%:那会让人以为方向对了,然后去调别的地方。 多处错误耦合时,部分修复给出的信号是反向的。

03公钥
量出来16 位和 44 位的安全素数生成出来一样大
真相是随机项写成了 random()*1e15远大于 2^(bits−2)

位数被那个随机项支配,bits 这个参数基本没起作用。 抓到它的是一个「本该成立的等式」:生成出来的 p 的二进制位数,应该等于你要的位数。 ⇒ 给每个生成器配一条自检不变量,比事后看结果像不像便宜得多。

04压缩
量出来「压缩过的输出还能再压 6 倍」。
真相是只能再省 0.19%

验这一条时用 LCG 造了一段假的「压缩输出」,而 LCG 的低位周期极短—— 造出来的根本不是高熵字节流,是一段有强结构的数据。换成真实范围编码器的输出, 鸽巢原理立刻现形。要验「压不动了」,就必须拿真的压缩输出,不能拿一个「看起来很随机」的东西。

05卡尔曼
量出来滤波器高估自己 1.3 倍(RMSE ÷ 自报 σ)。
真相是1.01——它是一致的。

瞬时 σ 去比整段 RMSE。σ 在收敛过程中一直变,取末尾那一刻的值当分母, 分子却是整段的均方根。两个数的时间窗口不一样,比值就没有意义。 这一处最像「正常」——1.3 倍完全在「滤波器略微乐观」的常见范围里。

08拥塞
量出来「没有拥塞控制」时吞吐 20%
真相是100%——连链路都没占满是模型的错。

没有拥塞控制时,每条流的窗口按对数均匀分配。N = 1 时它拿到区间最低端, 一条流根本填不满链路。⇒ 加了一条边界自检:N = 1 时开关两边必须给出同一个数 (拥塞控制是共享问题,只有一条流时它无事可做)。这条等式一立,问题当场暴露。

08拥塞
量出来排队时延偏低。
真相是暖机窗口太短,时延还没到稳态就开始统计

离散事件模拟的头几秒是瞬态:队列还在长,窗口还在爬。统计窗口必须跳过瞬态, 否则量到的是「系统正在启动」而不是「系统的稳态行为」。现在暖机 20 秒、统计 60 秒。

09单纯形
量出来随机 LP 上枢轴数只有 0.15m
真相是那族 LP 太软了(系数全正、约束不冲突)。

★ 这一处后来还翻出了更硬的一层:同一把标准扫描下,软那族的比值 随 m 单调下降0.32 / 0.26 / 0.22 / 0.19 / 0.17 / 0.17)—— 连「线性于 m」都不成立,所以从它身上读出来的那个 0.15m 根本不该被当成一个常数。 ⇒ 「随机生成的实例」不等于「有代表性的实例」。

10一致性哈希
量出来负载变异系数不服从 1/√V:V=50 和 V=100 都是 13%,而且不单调
真相是我用 N = 8 台机器在量它——8 个样本算出来的变异系数本身就是噪声

变异系数是个二阶统计量,用 8 个数估它,估计量自己的相对误差就有 1/√(2(N−1)) ≈ 27%。换成 N = 64 再量,CV × √V 在 V = 2…200 上是 0.94 ~ 1.06—— 1/√V 这条律干净得很,脏的是我的量法。 ⇒ 这一处最像 B 类:那组「不服从」的数看起来完全正常,只是它量的是估计量的抖动,不是被估的东西。

12分布式一致性
量出来用多数派(q=3)之后,可用率还是 100.0% ——「多数派不要钱」。
真相是我量的是集群可用率。换成客户端可用率是 88.1%

「集群可用率」问的是还有没有一块能提交。而分区只切成两块时, 多数派永远在某一侧,所以它恒等于 100%——这个数没有错,它只是没在回答那个问题。 代价落在少数派那一侧的客户端头上,它看不见。 ★ 问错了对象,就会量出一个漂亮的错数——而且这种错数最难发现,因为它连「可疑」都算不上。 ⇒ 换成「你连的那台副本所在的块能不能写」,代价立刻现形:100.0% → 88.1%。

13FM-index
量出来后向搜索只走 18 步,比逐位置比对快 7119 倍
真相是搜索是坏的——哨兵字符我排在了最大而不是最小, LF 映射整个错位,每次走到第 9 步区间就空了。真值是 200 步、800 倍。

这一处最能说明 B 类为什么危险。18 步「看起来完全正常」——后向搜索本来就该很快; 7119 倍「看起来完全正常」——它本来就该快几千倍。两个数都落在我预期的范围里,而且方向都对。 因为我只量了步数,没验答案。 加一条「两条路必须给出同一个命中数」,当场就抓到了(现在是 50 / 50)。 ⇒ 量一个算法有多快之前,先验它算得对不对——一个算错的实现往往更快, 因为它提前退出了。

C 类 · 6 处 · 2026-09-07 新增

检查在空转

这一类是把闸门第二关从「子串搜索」改成「键 → 值核对」时当场抓到的。 它比 B 类还隐蔽:B 类至少给出一个数,你还有机会觉得它不对劲; 这一类连看都没看——断言写得像在验,实际验的是别的东西,或者干脆是个恒等式。

03公钥
页面写着两档安全强度差 47000 倍
真值是65536 倍,正好 2¹⁶。

这是最典型的一处。闸门里那条断言是 ok('两者差 47000 倍', near(Math.pow(2,16), 65536, 1))—— 标签挂着 47000,验的却是一个恒真的等式;后面 synced 再确认页面上写着「47000 倍」这个字符串。 两条都绿,而数是错的。两档强度差 16 位,比值只可能是 2¹⁶。

02纠错码
页面写着255q + 2√(255q) < 163.6%
真相是那个方程解出来是 3.8%

3.6% 对应的是 Z ≈ 2.211,也就是 Φ⁻¹(1 − 1/74)——「74 个码字全部成功」那个分位数。 公式里写成 2 是个手滑,而闸门里干脆手抄了「x = 9.2 是这个方程的解」(它不是,真解是 9.75)。 ⇒ 数字是对的,推出它的那一行是错的——这种错最难发现,因为结果看着没问题。

09单纯形
页面写着区间 + 六个值 + 0.15m,共七个「实测」数。
真相是页面运行时根本不算它们——一次性实验手抄上去的。

这直接违反这个项目唯一那条规矩(屏幕上每个数字都必须是跑出来的)。 而闸门只卡了 0.35 < r < 0.70 这条两边都能过的松带子—— 页面写的 0.45~0.60 和闸门自己那套程序算出的 0.38~0.50 都在里面,从来没人比过。 ⇒ 现在标准扫描写死在 lib/simplex.js 里,第三关强制页面带同一份。

04压缩
页面写着33.5 倍 / 19.5 倍 / 7.8 倍。
真值是33.7 / 19.6 / 8.0 倍。

实现漂了,正文没跟。旧断言是 near(ratio, 33.5, 2.5) 这种容差带—— 它的职责是「结论还成立吗」,而正文那个数从来没被真比过。 ⇒ 容差带和精确值是两件事,都要有:一条管结论,一条管字面。

05卡尔曼
页面写着零偏归零后惯导漂 67 米
真值是68 米

同上,一处漂移。一米的差别不影响任何结论——但正因为不影响, 它才会一直待在那儿。⇒ 键→值核对不看重要性,只看等不等。

08拥塞
页面写着1906 ms / 31.0 倍 / 2.0 倍。
真跑之后三个数都是对的——但在那之前,没有任何东西在验它们

这一处结果无恙,性质却和上面几条一样:它们只有字符串核对, 「碰巧是对的」和「验过是对的」不是一回事。现在闸门会跑 B = 200 和 B = 6000 两组模拟, 把 bufferbloat 那条曲线的两端都钉住。

还有一处不在这三十七里,但它是同一种病

scripts/build.mjs 切 head 和 body 用的是 body.replace(/^…(?=<div class="wrap">)/, '$1')—— 那是把匹配到的前缀换成它自己,于是 head 等于整个 body,切出来的 <body> 是空串。 dist/ 里 11 个页面的内容全在 <head> 里。

没人发现,是因为浏览器会自动纠错:在 <head> 里遇到 <div> 就隐式关掉 head、开 body,把内容重新挂对地方,所以站点看着完全正常。 而 CI 唯一那条检查只看首行有没有 doctype,照样绿

⇒ 它不是一个数字错了,但病根一模一样:一个看着在验的检查,验的不是它声称的那件事。 现在 build 会当场断言切出来的 <body> 不少于 500 字符,CI 另加一条独立复查。

三条带走的话

三十七处摊完之后,剩下的不是「要小心」这种话,是三条可以照着做的。

  1. 没有一处是「算法本身出乎意料」 出乎意料的全都是我对它的描述。算法一直在那儿——打折的是那些流传很广的说法: 2m~3m、√机器精度、4096 倍、蒙特卡洛是水平线、运动补偿最值钱。 ⇒ 怀疑的对象应该是转述,不是原理。
  2. 三类各要一道不同的关,一道也省不掉 A 类靠先写下预期再实测(不写下来就没有「被打回」这回事); B 类靠同一件事用两份独立实现各算一遍(它的错数字看起来完全正常,只有第二份实现能戳穿); C 类靠把手抄的数换成槽位,由实现在运行时填,按键→值核对(它连一个可疑的数都不给你)。 ⇒ 三道关拦的是三种东西,重叠很少。
  3. 每一条订正之后的结论都比原来的强 因为它背后有个数。「关掉拥塞控制互联网就崩了」→「链路照样满载,塌的是公平性 0.997 → 0.461, 而真正塌吞吐的是重传定时器 100% → 6.2%」。后一句更长、更难说,但它是真的,而且能被重新验一遍。 ⇒ 被打回的记录比结论本身更有说服力:读者看到的不是一个宣称,是一个被检验过的宣称。