我在分子框里填 3.7,这一页拿去算的是 3
起因很普通:分子填 3.7、分母填 2,另一个分数保持 1/3,运算符停在加号。页面给出 11/6,小数格写 1.8333333333...,没一处提示我它把 3.7 换了。
把模板(11127 字节 / 231 行)的脚本块整段摘出来,回读的 7 个函数源码与模板原文逐字命中,再喂进纯 Node 的 vm 跑 calc()。只看这一页的代码,没开浏览器,重绘那一类账一律没测。
输入端:四个 number 框被不带 radix 的 parseInt 又读了一遍
四个框的类型都写着 number,代码却把它们的值再 parseInt 一遍,不带第二个参数。于是 3.7 在句点前停住得 3,3.99 同样得 3——是截断不是四舍五入,两格都出 11/6;填 -3.5 得 -3,结果 -7/6,小数格 -1.1666666667...。
剩下几格更容易看漏。填 1e3 读成 1,它在字母 e 那里就停了,出来的 5/6 与直接填 1 完全同形。填 0x10 读成 16,十六进制前缀被无 radix 的写法认了下来,界面给 25/3;带 radix 10 的那条路只会给 NaN。填 -0 读成 0,结果格剩 1/3。这几格都在分数计算器上,只改分子那一格、其余三格停在默认值就能复现。
再往空的一侧走就更安静:任一格填空串、abc、纯空格、Infinity,四个值在 isNaN 判定上直接 return,两张卡一起被隐藏,既不报错也不清空——实测前后步骤容器的 innerHTML 长度都是 338,旧表达式与步骤文本一个字节没掉,下一次合法输入踩着旧串覆写。错误出口那两条也不擦:分母填 0 写「分母不能为 0,请检查输入」,连前面的警告符合计 21 字节;除法时分数 B 的分子填 0 写另一句,恢复合法输入后旧串仍在,只是父容器被隐藏。
| 分子框填 | parseInt 读到 | 分数格 | 等值小数那格 |
|---|---|---|---|
| 3.7 | 3 | 11/6 | 1.8333333333... |
| 3.99 | 3 | 11/6 | 同上 |
| -3.5 | -3 | -7/6 | -1.1666666667... |
| 1e3 | 1 | 5/6 | 0.8333333333... |
| 0x10 | 16 | 25/3 | 8.3333333333... |
| -0 | 0 | 1/3 | 0.3333333333... |
图 1 只画读取那一步:在哪个字符前停住,就送哪个整数进算式。
显示端:这把尺是 14 个字符,不是小数位数
等值小数那一格判的是 String(val) 的字符数是否超 14:超线走 toFixed(10) 再拼三个 ASCII 句点,不超线原样打印。于是 1/2048 那 13 个字符原样上屏,1/4096 正好 14 个字符也原样上屏,1/8192 是 15 个字符就被砍成 0.0001220703...,一个本来整除、末尾还带 3 位有效数字的有限小数只因总长度超线换了口径。整数结果也归这把尺管。
不超线那一支的原样是字面意义的:1/10000000 那一格实测就是 1e-7,只有 4 个字符;1/1000000 是 0.000001。这条线只量十进制写法,4 个字符的指数串根本走不到判定之前。
超线那一支的第 11 位参与四舍五入:1/7 真值 0.142857142857... 的第 11 位是 5,本页显示 0.1428571429,末位比真值大 1;1/11 的第 11 位是 0,所以显示 0.0909090909。代码注释那句「有限小数最多显示12位」既不按 12 位、也不判是否循环。
图 2 那几行最直白:14 字符压线通过,15 字符被砍,4 字符的指数串没碰上这条线。
负分母那一步,同一行里有两把口径
公分母按绝对值算,通分因子却拿有符号的分母去除。于是 1/-2 加 1/3 的步骤行打成「公分母 = LCM(-2, 3) = 6」,紧接一行「分子 ×-3」,求和行「-3/6 + 2/6 = -1/6」:结果格符号是对的,同一个 -2 却一边被当参数显示、一边被绝对值吃掉。换成 1/-2 加 1/-3,两个因子都是负的,求和 -5/6。减法分支的通分行还不带「分子 ×k」那半句,与加法分支不同形。
符号的搬运只在 reduceFrac 那一步做,负号一翻面,约分行会凭空出现:-1/2 乘 -1/-3 的第一步打 1/-6,第二步打「GCD(1, -6) = 1」再打 -1/6,那个公约数等于 1,行却照样在。约分行的第一个参数也是有符号值,-1/2 加 -1/2 那行写「第三步:约分 GCD(-2, 2) = 2」,负号只出现在标签里。同一族另一页也有符号归属问题,那本账在单独做约分的那一页。
约分那一行是条件行,步数在 6 与 8 之间跳
1/2 加 1/3 的步骤卡只有 6 行,没有约分行;1/6 加 1/3 变 8 行,多出那行写「GCD(3, 6) = 3」;0/3 加 0/5 打「GCD(0, 15) = 15」,结果格是 0。判据一句话说完:公分母恒正,那一行出现当且仅当分子和与它的公约数大于 1,等于 1 时整块不拼进去,步数从 8 掉回 6。乘法分支是 2 行或 4 行,除法分支是 3 行或 5 行,3 除以 1/3 得 9 就不出约分行。分母为 1 时分数格只印分子,所以 0/3 乘 0/5 出的是单个 0,不是 0/1。
这把尺之外我只测到一处精度:99999999 乘 99999999 在这一页得 9999999800000000,BigInt 真值 9999999800000001,差 1。默认首屏就是 1/2 加 1/3 出 5/6,连调两次四个字段逐字节相同(表达式 11 字节、分数 3 字节、小数 15 字节、步骤原文 157 字节)。通分与约分该怎么教不在这篇,那是站内那篇算理文的范围。
Q:填 3.7 为什么一点提示都没有?
截断发生在读取阶段而不是校验阶段:parseInt 在第一个读不下去的字符前停住,剩下的 3 是个合法分子,页面没有理由报错。
Q:填 1e3 是想让它等于一千,为什么不是?
读取那一步在字母 e 处停住,读到 1,输出 5/6,与直接填 1 一模一样。
Q:约分那一行为时有时无,是漏渲染吗?
不是,它本来就是条件行;步数在 6 与 8 之间跳,说明判定走完了,只是那一块没被拼进去。