MapleCheng

在浩瀚的網路世界中無限潛水欸少年郎!

0%

ERP 系統的除法餘數怎麼處理?一個被忽略的精度陷阱

今天收到一封客戶來信,問了一個看似簡單的問題:

「配料移轉的時候,如果除不盡,你們是怎麼處理的?會把餘數補在最後一筆嗎?」

我愣了一下。老實說,這問題我還真沒想過。直覺回答:「應該是四捨五入吧?」然後去查了程式碼⋯⋯

結果發現事情沒那麼簡單。

問題背景

在製造業 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
2
// L76-78
ROUND(..., 4)

好,就是單純四捨五入到小數第 4 位,沒有補差機制。

然後我想說,那 MES 系統呢?會不會不一樣?去查了 WorkBatchMaterialNeedFeedDataDal.cs

1
2
// L17
ROUND(..., comUnit.DecimalDigits)

也是 ROUND,只是位數根據單位設定動態決定。還是沒有補差。

轉折來了

正當我準備回信說「我們所有模組都是四捨五入」時,我突然想到——等等,工時分攤好像也是類似的邏輯?去查了一下 WorkBatchBll.cs

1
2
3
// L654-660
var diff = total - summed;
lastItem.Quantity += diff;

⋯⋯

工時分攤有補差!

只有工時分攤這個模組,會在計算完所有分攤後,把總和跟原始的差值補到最後一筆。其他所有模組(配料移轉、MES 物料需求)都是單純 ROUND。

為什麼會這樣?

我跟團隊討論了一下,推測歷史原因可能是:

  1. 工時分攤:涉及薪資計算,必須精確到分毫,否則月底結算會對不起來。所以開發者特意加了補差邏輯。

  2. 物料移轉:早期開發時沒想那麼多,覺得四捨五入到小數第 4 位(0.0001 kg = 0.1 克)已經夠精確了,客戶不會在意那點誤差。

  3. MES:跟物料移轉類似,而且用的是單位設定的位數,理論上更靈活,但也沒有補差。

問題是,這套系統已經上線好幾年了,從來沒有人發現(或至少沒有人提報)這個問題。直到這次客戶主動問起。

要不要統一?

這就引出一個設計決策問題:要不要把所有模組統一成有補差機制?

贊成統一的理由:

  • 邏輯一致性,所有數值運算都精確
  • 避免未來其他客戶也提問
  • 工時分攤已經證明補差機制可行

反對統一的理由:

  • 現有客戶已經習慣目前的行為,突然改變可能引發新問題
  • 物料移轉的誤差極小(0.1 克),實際影響有限
  • 改動需要全面回歸測試,成本不低

最後我們的結論是:暫不主動改動,但記錄在案。如果未來有客戶明確要求,再針對該客戶的实例進行調整。

技術實作細節

如果你正在設計類似系統,這裡是兩種做法的程式碼範例:

單純四捨五入(現有做法):

1
2
3
4
SELECT 
MaterialId,
ROUND(TotalQuantity * Ratio, 4) as AllocatedQuantity
FROM MaterialAllocation
1
2
// C#
var allocated = Math.Round(total * ratio, 4);

最後一筆補差(工時分攤做法):

1
2
3
4
var items = CalculateAllocations(total, ratios);
var summed = items.Sum(x => x.Quantity);
var diff = total - summed;
items.Last().Quantity += diff;

或者更嚴謹的做法(避免浮點數誤差累積):

1
2
3
4
5
6
7
8
decimal runningTotal = 0;
for (int i = 0; i < items.Count - 1; i++)
{
items[i].Quantity = Math.Round(total * ratios[i], 4);
runningTotal += items[i].Quantity;
}
// 最後一筆用總和減去已分配的
items.Last().Quantity = total - runningTotal;

這個案例的教訓

1. 系統越大,越容易有「隱形不一致」

同一套系統、同一個團隊開發,不同模組卻用了不同的數值處理策略。這不是因為大家笨,而是因為:

  • 不同模組由不同人開發
  • 開發時間跨度長(幾年)
  • 沒有人做過全面的「數值精度審查」

2. 客戶是最好的 QA

這個問題在內部測試、code review 都沒被發現,直到客戶實際使用後提問。這提醒我:

  • 內部測試再怎麼全面,也覆蓋不了真實使用場景
  • 客戶提問不是麻煩,是免費的品質改進機會

3. 文檔的重要性

如果當初開發工時分攤的同事,在程式碼或設計文檔裡註記「此處採用補差機制,其他模組採用四捨五入」,今天我們就能更快回答客戶問題。

結論

ERP 系統裡的數值精度問題,看似小細節,實際影響可能很大。特別是涉及金錢、物料計量的場景,每一個小數點背後的決策都應該被記錄、被審查。

如果你的系統也有類似邏輯,建議做一次全面檢查:

  • 所有涉及除法的運算,餘數怎麼處理?
  • 不同模組的策略是否一致?
  • 如果不一致,是歷史原因還是刻意設計?
  • 有沒有文檔記錄這些決策?

別等到客戶問起才來查程式碼。那時候就太晚了。