工作幾年了,也做很多項目,其中就碰到過很多金額用了各種數據類型,有的項目用double,有的用BigDecimal,有的用Long,那么就產生一個疑問金額到底應該用什么數據類型?
很早之前, 記得一次面試, 面試官問存儲金錢用什么數據類型? 當時只知道8種數據類型(boolean, byte, short, int, long, float, double, char)的我, 回答了double, 因為我覺得double是雙精度類型, 最適合, 但是面試官告訴我應該用BigDecimal! 最近在做支付的項目, 才對這種數據類型有了更多的了解.
1、第一種建議使用BigDecimal
栗子
請看題:
示例1
問, 結果是多少? 0.01?
No! 結果是0.009999999999999998!
為什么會這樣呢? 因為float和double都是浮點數, 都有取值范圍, 都有精度范圍. 浮點數與通常使用的小數不同, 使用中, 往往難以確定. 常見的問題是定義了一個浮點數, 經過一系列的計算, 它本來應該等於某個確定值, 但實際上並不是! 金額必須是完全精確的計算, 故不能使用double或者float, 而應該采用java.math.BigDecimal或者Long
加減乘除
兩個BigDecimal值應該怎樣進行加減乘除呢? +, -, *, / 這樣寫嗎? 不!
請看示例:
示例2
加減乘除使用了英文的加減乘除, 即add, substract, multiply和divide
大小比較
兩個BigDecimal值怎么比較大小呢? 能用>或者<嗎? 也不可以!
示例3
兩個BigDecimal值比較使用compareTo方法, 比較結果有-1, 0, 1, 分別表示小於, 等於, 大於; 對於0, 可以使用BigDecimal.ZERO表示!
小數位數及四舍五入規則
在項目中, 涉及到稅費的計算, 計算的結果可能是小數點后面十幾位, 那么怎么進行結算呢? 這就需要四舍五入這種東東了.
示例4
其中setScale的第一個參數是小數位數, 這個示例是保留2位小數, 后面是四舍五入規則.
mysql數據庫設計
BigDecimal在進行入庫時, 數據庫選擇decimal類型, 長度可以自定義, 如18; 小數點我們項目中用的是2, 保留2位小數. 此外還要注意的就是默認值, 一定寫成0.00, 不要用默認的NULL, 否則在進行加減排序等操作時, 會帶來轉換的麻煩!
`balance` decimal(18,2) DEFAULT '0.00' COMMENT '賬戶余額',
2、第二種建議推薦使用Long
如果使用Long就得在保存的時候乘以10000(或者其他適合倍數),前端展示的時候再除以10000,前端展示的時候使用json序列化自動除以10000。
直接上代碼:
package com.example.test; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import java.math.BigDecimal; import java.text.NumberFormat; public class MoneySerializer extends JsonSerializer<Long> { /** * 縮放倍數,調整需要慎重,目前會影響到的有兩處: * 1、金額 * 2、庫存 */ public static final BigDecimal MULTIPLE = BigDecimal.valueOf(10000); @Override public void serialize(Long num, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (num != null) { NumberFormat numberFormat = NumberFormat.getInstance(); numberFormat.setMinimumFractionDigits(2); gen.writeString(numberFormat.format(this.reduce(num))); } } /** * 縮小(四舍五入,保留兩位小數) * * @param num 需要縮小的數值 * @return */ public Double reduce(Long num) { return reduce(num, 2); } /** * 縮小(四舍五入) * * @param num 需要縮小的數值 * @param scale 小數點后位數 * @return */ public Double reduce(Long num, int scale) { return num == null ? null : BigDecimal.valueOf(num).divide(MULTIPLE, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
然后加注解:@JsonSerialize(using = MoneySerializer.class)