今天收到一封客戶來信,問了一個看似簡單的問題:
「配料移轉的時候,如果除不盡,你們是怎麼處理的?會把餘數補在最後一筆嗎?」
我愣了一下。老實說,這問題我還真沒想過。直覺回答:「應該是四捨五入吧?」然後去查了程式碼⋯⋯
結果發現事情沒那麼簡單。
問題背景
在製造業 ERP 系統裡,「配料移轉」是個常見操作:一批物料要分給多個工單使用,需要按比例分配。例如:
- 總物料:100 kg
- 工單 A:30%
- 工單 B:30%
- 工單 C:40%
聽起來很簡單,100 × 0.3 = 30 嘛。但現實是,比例可能不是整數,除法可能除不盡:
- 總物料:100 kg
- 工單 A:33.333…%
- 工單 B:33.333…%
- 工單 C:33.334…%
這時候怎麼辦?每筆都四捨五入?那最後加起來可能不等於 100。最後一筆補差?那前面的帳怎麼對?
查程式碼時間
我先去看了配料移轉的程式碼(ScmMaterialDispensingDataModel.cs):
1 | // L76-78 |
好,就是單純四捨五入到小數第 4 位,沒有補差機制。
然後我想說,那 MES 系統呢?會不會不一樣?去查了 WorkBatchMaterialNeedFeedDataDal.cs:
1 | // L17 |
也是 ROUND,只是位數根據單位設定動態決定。還是沒有補差。
轉折來了
正當我準備回信說「我們所有模組都是四捨五入」時,我突然想到——等等,工時分攤好像也是類似的邏輯?去查了一下 WorkBatchBll.cs:
1 | // L654-660 |
⋯⋯
工時分攤有補差!
只有工時分攤這個模組,會在計算完所有分攤後,把總和跟原始的差值補到最後一筆。其他所有模組(配料移轉、MES 物料需求)都是單純 ROUND。
為什麼會這樣?
我跟團隊討論了一下,推測歷史原因可能是:
工時分攤:涉及薪資計算,必須精確到分毫,否則月底結算會對不起來。所以開發者特意加了補差邏輯。
物料移轉:早期開發時沒想那麼多,覺得四捨五入到小數第 4 位(0.0001 kg = 0.1 克)已經夠精確了,客戶不會在意那點誤差。
MES:跟物料移轉類似,而且用的是單位設定的位數,理論上更靈活,但也沒有補差。
問題是,這套系統已經上線好幾年了,從來沒有人發現(或至少沒有人提報)這個問題。直到這次客戶主動問起。
要不要統一?
這就引出一個設計決策問題:要不要把所有模組統一成有補差機制?
贊成統一的理由:
- 邏輯一致性,所有數值運算都精確
- 避免未來其他客戶也提問
- 工時分攤已經證明補差機制可行
反對統一的理由:
- 現有客戶已經習慣目前的行為,突然改變可能引發新問題
- 物料移轉的誤差極小(0.1 克),實際影響有限
- 改動需要全面回歸測試,成本不低
最後我們的結論是:暫不主動改動,但記錄在案。如果未來有客戶明確要求,再針對該客戶的实例進行調整。
技術實作細節
如果你正在設計類似系統,這裡是兩種做法的程式碼範例:
單純四捨五入(現有做法):
1 | SELECT |
1 | // C# |
最後一筆補差(工時分攤做法):
1 | var items = CalculateAllocations(total, ratios); |
或者更嚴謹的做法(避免浮點數誤差累積):
1 | decimal runningTotal = 0; |
這個案例的教訓
1. 系統越大,越容易有「隱形不一致」
同一套系統、同一個團隊開發,不同模組卻用了不同的數值處理策略。這不是因為大家笨,而是因為:
- 不同模組由不同人開發
- 開發時間跨度長(幾年)
- 沒有人做過全面的「數值精度審查」
2. 客戶是最好的 QA
這個問題在內部測試、code review 都沒被發現,直到客戶實際使用後提問。這提醒我:
- 內部測試再怎麼全面,也覆蓋不了真實使用場景
- 客戶提問不是麻煩,是免費的品質改進機會
3. 文檔的重要性
如果當初開發工時分攤的同事,在程式碼或設計文檔裡註記「此處採用補差機制,其他模組採用四捨五入」,今天我們就能更快回答客戶問題。
結論
ERP 系統裡的數值精度問題,看似小細節,實際影響可能很大。特別是涉及金錢、物料計量的場景,每一個小數點背後的決策都應該被記錄、被審查。
如果你的系統也有類似邏輯,建議做一次全面檢查:
- 所有涉及除法的運算,餘數怎麼處理?
- 不同模組的策略是否一致?
- 如果不一致,是歷史原因還是刻意設計?
- 有沒有文檔記錄這些決策?
別等到客戶問起才來查程式碼。那時候就太晚了。