雜記 › 技術筆記 › 小數位數與捨入方式:補零一關掉,12.30 就回不去了

小數位數與捨入方式:補零一關掉,12.30 就回不去了

一、回填之後,12.30 再也回不去 12.30

上週把一張對帳表統一成 2 位小數,匯出之前順手取消了補零。第二天同事來問:3 月那行 12.30 怎麼只剩 12.3,兩位的帳面精度對不上了。遺失發生在格式化的那一刻,回寫之後找不到原始字串。

那一頁很薄:輸入框、一根保留小數位數的滑桿、一個捨入方式下拉、補零與千分位兩個勾選框,外加「處理數值數」和「跳過無效項」兩個計數。滑桿只能停在 0 到 10,一共十一檔,預設 2 位;除此之外頁面上沒有任何關於取整方向、精度損失、科學計數法的提示文案。

二、四個捨入選項在負數上只剩兩個方向

下拉裡排著四件事:四捨五入、向下取整(地板)、向上取整(天花板)、截斷(直接捨去)。把 -2.5 取 0 位,四條分別給 -3、-2、-3、-2——四個名字、兩個結果:floor 與 trunc 逐條同值,round 與 ceil 在 .5 上同值,「四種模式給出四種結果」在這裡不成立。

原因在唯一的算式裡:四個分支都先對絕對值取整,再按符號把負號補回來。取整發生在絕對值軸上,負數上的「向下」其實是朝零,落到 -2。跟原生函數對一遍更清楚:Math.floor(-2.5) 是 -3,頁面的向下取整給 -2;Math.round(-2.5) 是 -2,頁面的四捨五入給 -3。-2.4 更徹底:-2、-2、-3、-2,三缺一。

-4 -3 -2 -1 輸入 -2.5 四捨五入 -3 離零 向下取整 -2 朝零 向上取整 -3 離零 截斷 -2 朝零 floor 與 trunc 同值,round 與 ceil 同值

圖 1:-2.5 取 0 位只剩兩個落點

三、1.005 保留 2 位為什麼得到 1.00

這一段沒有方向可言,只有二進制近似值落在哪一側。第三位小數正好是 5 的 8 個樣本,取 2 位、四捨五入,5 個被捨下、3 個被進上:

輸入乘 100 的中間值輸出
1.005100.499999999999991.00
8.995899.49999999999998.99
1.015101.499999999999991.01
1.045104.51.05
1.055105.51.06
2.675267.52.68

分界不是「見 5 就進」,而是乘完 100 之後那個二進制近似值有沒有嚴格壓過 .5:100.49999999999999 在 100.5 之下,捨;104.5 正好落在 .5 上,進。整條鏈就是乘一次 10 的位數次方、取整、再除回去,沒有字串移位、也沒有十進位庫參與——代碼裡那句「修正負數四捨五入」的註釋只修了符號,沒修精度。錢數只要第三位可能是 5,方向就由不得我。

四、補零一關,尾隨零永久遺失

回到開頭那次故障。補零預設是勾著的,標籤寫著「補零(如 3.5 → 3.50)」。勾著時 12.3 取 2 位得 12.30,取消勾選就只出來 12.3;同一批裡 100 從 100.00 退回 100,1.005 更直接——連小數都不剩,輸出 1。尾隨零在數值層面本來就不存在,輸出的是字串,回寫後沒有任何地方還留著「原本按 2 位記」。

位數 0 時補零完全不介入:5 仍是 5、5.5 截斷仍是 5,勾得再堅決也看不到 .00。位數檔位本身也不負責補零,12.3 取 4 位、關著補零,輸出還是 12.3。

五、批次貼上:三條版本號全部「成功」,其中一條已變成整數

批次入口是真的:輸入按換行、半形逗號、全形逗號三種分隔切成 token,一次跑完整批,全形與半形完全等價。我把一列版本號貼進去,取 1 位、選截斷。

輸入 輸出 判定 1.2.3 → 1.2 已格式化 10.0.1.25 → 10 已變成整數 2.2.3 → 2.2 已格式化 取 1 位、截斷:count=3 skipped=0 沒有一行報錯,資料已經變形

圖 2:三行版本號 count=3、skipped=0,其中一行已被截成整數

沒有一行報錯,也沒有一行被標掉,跳過無效項老老實實是 0,第二條其實只剩一個 10。計數只回答「能不能讀成數」,不回答「讀成了什麼」。

混排一批更能說明問題:5 行文字得到 tokens=6、count=4、skipped=2——帶千分位的那一行被逗號切成 2 個 token,於是編號 #1 到 #6 比行數多,它是切分之後的下標、不等於行號。同一批裡 2024.01.15 被寫成 2024.01、1.2.3 被寫成 1.20,而「價格 3.5 元」和 v1.2.3 整行判無效:它只認每個 token 開頭的那一個數,行中間的 3.5 不會被提取。

「複製全部」是第二條管線,無效項按原文回填:那批貼出來是 1.00|234.56|价格 3.5 元|v1.2.3|2024.01|1.20,螢幕上標紅的 2 行變成 2 行原文混在同一份資料裡。反過來說,整批全無效時結果面板也不回落空態:三行 abc 給 tokens=3、skipped=3,列表照樣顯示,只有完全空的輸入才退回「請輸入數值後自動格式化」。

六、越界的那一頭:1e21 與長整數

1e21 取 2 位輸出 999999999999999900000.00——只是乘 100 再除 100,原值少了 100000000000;取 0 位時不乘不除,科學計數法原樣透出 1e+21。1e-7 取 10 位再補零得到畸形串 1e-7.0000000000,千分位在這種指數串上一個逗號也加不上,同一個數取 2 位則直接是 0.00。

長整數更安靜:9007199254740993 進了 parseFloat 就成了 9007199254740992,末位變了而介面看不出區別;123456789012345.67 取 2 位被寫成 123456789012345.69,整數部分已經吃掉 15 位有效數字。訂單號、卡號這類值不該進這條路,想按別的進位制讀它們得去進位轉換那篇——同一串字元,這一頁連 0x10 都不認。

這幾個開關在小數位數格式化那一頁都能復現:同一份輸入只動一個勾選框,把兩個計數一起讀。跑完一批,我會把輸入與輸出各貼一份進兩段文字對比,確認差額只落在該落的那幾行。

FAQ

Q:負數上的「向下取整」到底往哪邊?

往零。四個分支都對絕對值取整再補負號,-2.5 取 0 位給 -2,與截斷同值;真正的向下是 -3,這一檔反倒由「向上取整」給出。

Q:能拿它統一金額保留 2 位嗎?

第三位可能是 5 的時候不行:1.005 → 1.00、8.995 → 8.99,而 1.045 → 1.05、2.675 → 2.68。要它當顯示層可以,當結算層不行。

Q:整批都是無效數值,為什麼列表還在?

回到空態要求兩個計數同時為 0,三行 abc 是 count=0、skipped=3。列表非空不等於跑成功,那兩個數才是要讀的東西。

📎 相關文章

文本合併後行數對不上:缺尾末換行、被吃掉的 BOM 和不可靠的順序 2026-10-26 Excel 貼上轉 JSON:日期、長數字與精度在貼上那一刻就已經變了 2026-10-26 條碼解碼只吐原文:EAN-13 檢查碼、UPC-A 的前導零與 CODE128 的模 103 2026-10-26 二維碼解碼實測:糾錯等級救不了遮擋,每模組 2 像素才是生死線 2026-10-26 字頻還是詞頻:同一句中文,一份 23 項榜單和一份空表 2026-10-26