我在分子框裡填 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 之間跳,說明判定走完了,只是那一塊沒被拼進去。