Backend Engineering·2026年4月28日·9 分鐘閱讀

金流系統的金額為什麼不能用 float:Decimal 與整數分的實戰

L
Louis Wu

後端工程師,長期處理交易所與金流系統的金額運算,對數值精度有強迫症。

這是我面試後端工程師時很愛問的一題:「金額為什麼不能用 float?」聽起來基礎,但能完整講清楚、又知道實務上怎麼解的人,其實不多。這篇把我在交易所和金流系統累積的金額處理經驗講一遍。

問題的根源:二進位浮點存不下 0.1

float(IEEE 754)是用二進位來逼近數值的。但 0.1 在二進位是一個無限循環小數,就像 1/3 在十進位寫不完一樣。所以電腦存的 0.1 其實是一個非常接近、但不等於 0.1 的值。

於是會發生這種事:

  • 0.1 加 0.2 算出來不是 0.3,而是 0.30000000000000004
  • 累加很多筆小金額後,誤差會慢慢放大
  • 兩個「看起來相等」的金額用等號比較卻不相等

在一般場景這點誤差無所謂,但在金流:對帳會差一分錢、成交價會對不起來、利息計算會被稽核打槍。這是正確性問題,不是效能問題,沒有妥協空間。

解法一:用整數存最小單位

最簡單、效能也最好的解法:不要存「元」,存「分」(或你的幣別的最小單位)。

  • 台幣 100.50 元,存成整數 10050(分)
  • 所有加減都在整數域進行,完全沒有精度問題
  • 用 int64 足夠表達非常大的金額

要注意的是:

  • 顯示時才除以 100 格式化成「元」
  • 牽涉到除法、比例、利率時要小心——整數除法會無條件捨去,要明確決定四捨五入或無條件捨去的規則,並且捨入規則要全系統一致
  • 跨幣別時,不同幣別的小數位數不同(日圓沒有小數,有些加密貨幣到小數點後 18 位),要把「小數位數」當成幣別的屬性

加密貨幣那種到小數點後 8 位、18 位的,int64 可能不夠,這時就要用大整數或 decimal 型別。

解法二:用 Decimal 型別

如果運算包含很多比例、百分比、複雜的財務計算,純整數會讓程式碼變得很難讀。這時用專門的 decimal(十進位定點數)函式庫會更清楚——它內部用十進位表示,不會有二進位浮點的逼近問題,而且能精確控制小數位數和捨入模式。

Go 生態裡有成熟的 decimal 套件。選用時我會注意:

  • 是否支援明確指定捨入模式(銀行家捨入 vs 四捨五入)
  • 序列化進 DB 與 JSON 時是否保持精度(別在邊界又轉回 float)
  • 效能:decimal 比整數慢,熱路徑(像撮合核心)我還是會用整數,周邊計算才用 decimal

別在邊界把精度漏掉

最常見的暗坑不是運算本身,而是資料在不同層之間流動時偷偷被轉成 float:

  • 資料庫:用 DECIMAL/NUMERIC 欄位,不要用 FLOAT/DOUBLE
  • JSON:JavaScript 的 number 是 float,大數或高精度金額用字串傳遞,別用 number
  • ORM 對應:確認框架沒有偷偷把 DECIMAL 讀成 float
  • 第三方 API:對方回傳的金額字串,自己用 decimal/整數去解析,別經過 float

我看過最痛的 bug,就是整條鏈路都用 decimal,結果在某個 API 邊界 JSON 解析時被轉成 float 又轉回來,精度就在那一瞬間掉了,對帳每天差幾塊錢查了一個禮拜。

小結

  • float 是二進位逼近,存不下 0.1,金額用了必出事
  • 熱路徑用整數存最小單位,效能最好
  • 複雜財務計算用 decimal 型別,並明確指定捨入規則
  • 真正的魔鬼在邊界:DB 欄位、JSON、ORM、第三方 API 都要確認精度沒被 float 偷走

金額處理是後端的基本功,也是區分「能跑」和「能上線管錢」的分水嶺。一分錢的誤差,在金流系統裡就是一個必須解釋清楚的事故。

#Go#金額#Decimal#精度#金流

相關文章