一、三个数用顿号分开,结果只剩一个数
要算 48、36、60,我按中文习惯用顿号分开敲进输入框,等来的是空白:结果区、过程区、因数区、统计区同时隐藏。
谜底在模板第 68 行的字符类:split(/[,,\s]+/) 只有 3 个成员——半角逗号、全角逗号、JS 的 \s。换行、Tab、全角空格 U+3000 都切得开,但顿号 U+3001、分号、竖线、斜杠、中文句号不在名单里:"48、36、60" 整串被当成一个段,parseInt 撞到顿号就停,解析只剩 [48],后两个数静默消失。混用更阴,"12,18、24" 得到 [12, 18] 和 6,看着像算对了。
二、非法段从不炸整批,它只是安静地消失
第 72 行的 !isNaN(n) && n > 0 是唯一白名单,脚本里 try 与 catch 各 0 次,坏段炸不了整批,只是自己消失:
| 输入 | numbers | 页面表现 |
|---|---|---|
"3.9,4.1" | [3, 4] | 小数只截不进 |
"0x10,20" | [20] | 第 71 行写了 radix=10 |
"-12,18" | [18] | 负数进不到算法 |
"48,36" | [] | 全角数字一个不认 |
绕过第 72 行、直接喂给第 111 行的 gcd2WithSteps 才看得到函数本体:(-12,-18) 返回 -6,全脚本没有 Math.abs;(0,0) 是 0 步。这条分支在浏览器里不可达。
三、多值是从左往右折的,顺序与零行都会改行数
页面唯一在用的是第 111 行记步数的 gcd2WithSteps,第 101–108 行那个 gcd2 是死代码,所以最大公因数计算的每行除法都能数。
多值是严格左折叠(第 126–136 行),不排序也不去重:gcdMulti([48,36,60,72]) 分 3 组共 6 步得 12,而 [60,48,36] 只要 4 步、[36,48,60] 要 5 步。差的那步来自第 115–117 行——只要 a < b,第一条必然写成 a = b × 0 + a 这种 0 商交换行。第 117 行 push 无条件,余数为 0 的收尾行照打,(65536,4096) 那 1 行除法也要占 4 行。行数换算式:两数时 = 步数 + 3,k 个数时 = 各组(步数 + 3)加 (k − 2) 个衔接行,实测 48,36,60 是 10 行、6 个数是 25 行。
版面是 48 = 36 × <span class="highlight">1</span> + 12,余数为 0 就写「+ 0」,不写共几步。
四、最坏输入确实是相邻斐波那契对,可页面没有步数栏
相邻斐波那契对顶到最坏:F(31)=1346269 与 F(30)=832040 实测 29 个除法步、打出 32 行、过程 HTML 长 2488 字符;反序变 30 步 33 行。规律是步数 = 较大者下标 − 2,F(41)=102334155 与 F(40)=63245986 实测 38 步。步数与行数均为本机 Node v24 跑模板第 60–209 行的脚本口径。
同量级随机对照(LCG seed=20261027,各 2000 对)最大 22 步 @(923408,1247609)、平均 12.134 步,少 7 步。拉梅那句「步数不超过较小数位数的 5 倍」在这份代码里成立:limit=1000、1000000、1000000000 各抽 30000 对共 9 万对,违例 0 对,最紧的 (458528519,627075196) 是 30 步对 9 位数、上界 45。可第 189–192 行统计栏固定 4 项、0 个步数字段。
五、错在第 1 位的是 parseInt,不是辗转相除
脚本里 BigInt 出现 0 次,第 71 行 parseInt 返回双精度(上限 9007199254740991),取模跑在第 116 行的 Number 上;第 111 到 122 行的循环与 BigInt 真值逐步一致,误差全在解析。
最刺眼的是 9007199254740993,9007199254740992:相邻两数折成同一个,页面显示 "9,007,199,254,740,992" 而真值 1,差异从第 1 位就开始。再小一档:9007199254740993,3 页面给 1、真值 3;12345678901234567891,2 页面给 2、真值 1,连输入本身都被写成 12345678901234567000。
六、把标签页跑死的是因数表,不是欧几里得
同一条 20 位输入把耗时拆开称:第 160 行 gcdMulti 只花 0.06 毫秒就得 gcd=2(2 步除法),第 181 行为每个输入数调用的 getFactors 却单独花 35654 毫秒——第 141 行的上界 i * i <= n 迭代 3513641828 次,只给 96 个因数。整条链路同步阻塞 30798 毫秒,1e20 那条是 83505 毫秒,30 位输入在 240000 毫秒内没输出就被 kill。
第 76 行末尾直接调 calcGcd,代价落在每次按键之间;1e20 那条最终也只写出 441 个因数。
七、规模崩点不可复现:124000 过、125000 抛
每多 1 个数固定多 4 行(行数 = 4n − 4):1000 个数 3996 行、328726 字符;100000 个数 399996 行、标签区 3800000 字符、常驻内存 312 MB。崩点在第 191 行的 Math.max.apply(null, numbers):全新进程里 124000 个数还能写出统计栏,125000 个数必抛 RangeError 且统计栏留空,而第 177 行过程区与第 186 行因数区那时已写完,页面停在「有答案、没统计」的半渲染态;同一进程连跑 5 次 200000 个数又全部通过。所以「最多支持 N 个数」在这页站不住。
可信的是算法那圈:零行肯打给你,行数还能反推步数。不可信的是外面那层——分隔符、过滤与因数表都不兜底。
常见问题
Q:三个数用顿号分开为什么只算了一个?
第 68 行只认半角逗号、全角逗号、空白这 3 个成员,顿号不在里面;不足 2 个数时第 154–157 行只把 display 设成 none,页面不解释。
Q:页面上有没有算好的步数?
没有,统计栏那 4 项里不含步数;要步数只能数过程区的行数,两数时减 3。
Q:粘贴一个二十位数为什么就卡死了?
卡的是第 181 行那张因数表:它单独花 35654 毫秒,而第 160 行的辗转相除只要 0.06 毫秒。parseInt 还早把它折成了 12345678901234567000。