爭論不休的一個話題:金額到底是用Long還是BigDecimal?
在網上一直流傳著一個爭論不休的話題:金額到底是用Long還是用BigDecimal?這個話題一出在哪都會引起異常無比激烈的討論。。。。 比如說這個觀點:算錢用BigDecimal是常識
圖片
圖片
有支持用Long的,將金額的單位設計為分,然后乘以100,使用Long進行存儲以及計算,這樣不用擔心小數點問題。
圖片
并且一些銀行系統就會選擇用Long
圖片
還有,最最最牛逼的萬能大法:用String
圖片
成年人不做選擇題,Long跟BigDecimal都用。。。
圖片
還有一種就是封裝一個金額的基類,對金額進行統一處理。
圖片
排除float和double
當然,對于金額,首先我們要排除的就是float和double。它們不適合用于精確的金融計算,因為float和double是基于IEEE 754標準的浮點數表示,它們無法精確地表示所有的十進制小數。這會導致在進行財務計算時出現舍入誤差,這些誤差可能會累積并導致不可預測的結果。
關于帶精度的計算,我們不推薦使用float以及double,推薦使用BigDecimal,具體原因請參考:聊一聊_BigDecimal_使用時的陷阱
選擇Long
Long類型在Java中用于存儲64位整數。它的主要優點是速度快,因為整數運算在CPU層面是非常高效的。另外,Long類型也占用較少的內存,并且整數類型(BIGINT)在數據庫中占用較少的存儲空間。
但是Long類型在處理金額時有幾個明顯的缺點:
1. 精度問題:Long只能存儲整數,無法直接表示小數。使用Long來表示以分為單位的金額(例如,100表示1元),此時就會失去小數的精度。即使使用某種方式來表示小數(例如,乘以100或10000),也會遇到舍入誤差的問題。并且這種計算方式也會增加計算的復雜度。
2. 浮點數問題:雖然這不是直接使用Long的問題,但如果你嘗試將Long與浮點數(如double或float)進行轉換以進行計算(比如匯率計算等),還是會遇到浮點數精度問題,這可能導致在財務計算中出現不可接受的誤差。
在阿里巴巴的開發手冊中建議使用Long。
阿里巴巴開發手冊.png
但是在一些金融系統當中,對小數位要求比較高的,比如精確到小數點后6位,那么我們使用Long進行存儲,每次在計算時都要除以或者乘以1000000,那么計算的開銷就很大了。
并且,如果在需求確認時,我們無法知道金額要求的小數位,那我們使用Long也是不行的,我們并不知道需要乘以或者除以多少個0。
選擇BigDecimal
BigDecimal是Java提供的一個類,用于任意精度的算術運算。它的主要優點是提供了高精度的計算,這對于金融和貨幣計算來說是非常重要的。BigDecimal可以表示任意大小的正數、負數或零,并可以精確控制舍入行為。并且在數據庫中存儲時也有對應的類型進行匹配,比如MySQL的DECIMAL類型提供了精確的數值存儲,可以匹配BigDecimal的精度。
但是BigDecimal也有一些缺點:
- 1. 性能:與Long相比,BigDecimal的性能較差。因為它的運算需要更多的內存和CPU時間。
- 2. 復雜性:使用BigDecimal進行運算比使用Long或基本數據類型更復雜。你需要考慮舍入模式、精度等因素。
- 3. 在數據庫中需要更多的存儲空間來存儲小數部分。
而在Mysql的開發手冊中,建議金額需要進行小數位計算時,存儲要使用Decimal,否則我們要將金額乘以對應小數位的倍數變成BIGINT進行存儲。
Mysql開發手冊.png
總結
基于上述對Long和BigDecimal的優缺點分析,我們可以得出以下結論:
在金額計算層面,即代碼實現中,推薦使用BigDecimal進行所有與金額相關的計算。BigDecimal提供了高精度的數值運算,能夠確保金額計算的精確性,避免了因浮點數精度問題導致的財務誤差。使用BigDecimal可以簡化代碼邏輯,減少因處理精度問題而引入的復雜性。
而在數據庫存儲方面,我們需要根據具體需求進行權衡。如果業務需求已經明確金額只需精確到分(如某些國家/地區的貨幣最小單位為分),并且我們確信不會涉及到需要更高精度的小數計算,那么可以使用Long類型進行存儲,將金額轉換為最小貨幣單位(如分)進行存儲。這樣可以節省存儲空間并提高查詢性能。
但是如果業務需求中金額的小數位數不確定,或者可能涉及多位小數的計算(如國際貨幣交易等),那么最好使用DECIMAL或NUMERIC類型進行存儲。這些類型提供了精確的數值存儲,可以確保數據庫中的數據與應用程序中的BigDecimal對象保持一致,避免數據轉換過程中可能引入的精度損失。