你有用對型別存金額嗎?
先說個冷知識,你知道根據 RFC 4217 的定義,新臺幣可以到小數點(decimal point)後 2 位嗎?
數值一但牽扯到小數點,就會有所謂的浮點數精度問題(floating-point precision issues), 因為浮點數沒有精確的 2 進制表示法,所以它其實是 1 個極度近似值,你以為數值的 0.3 ,對電腦而言可能是 0.30000000000000004 。
這種問題會對電商、加密貨幣交易所帶來困擾(比特幣可以到小數點後 8 位數,這個單位又稱 satoshi, 也就是中本聰的聰 ),畢竟金額、價格應該多少就該多少,不能多也不能少,所以在儲存金額、價格時都要特別注意。
資料庫儲存金額數值(monetary values)有 2 種方法:
使用 Decimal 型別,有別於 Float, Double 這類型別, Decimal 型別不會產生浮點數精度問題,使用 Decimal 可以定義精度,例如 Decima(5, 2) 就代表可以顯示 5 位數,包含小數點後 2 位數,所以可以儲存數值 -999.99 到 999.99 。
使用 Integer 型別,這個解法的思路就是把產生問題的小數點給解決掉。具體怎麼解決?例如金額 10.25 ,就將金額乘以 100 變成 1025 存起來,要使用的時候再除以 100 還原即可。
這 2 種解法各有優缺,用 Decimal 的讀寫效率比用 Integer 差一些,但是用 Integer 的話會在需要增加小數點位數時,對資料庫的資料以及程式碼做變動,譬如從 2 位變 3 位時,資料庫所有數值都得再額外乘以 10 更新,程式碼在存數值之前或讀數值之後都得改為乘以 1,000 或除以 1,000 。
此外,還有 1 種情況也是用 Integer 需要注意的,例如訂單金額算出來是 3.2 USD, 手續費是 0.1 USD 時,當你在程式碼中做加總顯示總金額時,就有可能再遇到浮點數精度問題,這個可以用以下 Python 程式碼示範:
>>> 320/100 + 10/100
3.3000000000000003
當然,最好這種加總可以也先算好 330 再存進去資料庫避免浮點數精度問題,或者交給資料庫計算,例如 MySQL 對於 320/100 這種運算會預設轉為 Decimal, 所以加總也不會有浮點數問題,見圖 1, 圖 2 。
圖 1:
圖 2:
以上,就是關於如何儲存金額的探討。