本文首發於我的微信公衆號《andyqian》, 期待你的關注!java
在Java中,咱們一般使用 BigDecimal 類型來表示金額,特別是在金融,財務系統中,使用的特別多。例如:轉帳金額,手續費等等。今天就一塊兒來認識下BigDecimal。微信
在此以前,咱們先來說講爲何要使用 BigDecimal ?而不是Float,Double類型?其實光從表現形式來看,Float,Double,BigDecimal 類型都能表示小數。其區別在於精確計算時,Float 與 Double 類型都會損失精度,固然了,BigDecimal 使用不正確時,也會損失精度。在金融系統中,金額計算是最基本的運算,精度的丟失是絕對不能容忍的。接下來,咱們來看看下面的例子:ide
Float 類型:函數
public void testFloat(){ float a = 1.1f; float b = 0.8f; System.out.println("a-b = "+(a-b)); System.out.println("a+b = "+(a+b)); System.out.println("a*b = "+(a*b)); System.out.println("a/b = "+(a/b)); }
結果以下:測試
a-b = 0.3 a+b = 1.9000001 a*b = 0.88000005 a/b = 1.375
Double 類型:spa
public void testDouble(){ double a = 1.1; double b = 0.8; System.out.println("a-b = "+(a-b)); System.out.println("a+b = "+(a+b)); System.out.println("a*b = "+(a*b)); System.out.println("a/b = "+(a/b)); }
結果以下:線程
a-b = 0.30000000000000004 a+b = 1.9000000000000001 a*b = 0.8800000000000001 a/b = 1.375
BigDecmial 錯誤使用:3d
public void testBigDecimal(){ BigDecimal a = new BigDecimal(1.1); BigDecimal b = new BigDecimal(0.8); System.out.println("a-b = "+(a.subtract(b))); System.out.println("a+b = "+(a.add(b))); System.out.println("a*b = "+(a.multiply(b))); System.out.println("a/b = "+(a.divide(b))); }
結果以下:code
a-b = 0.3000000000000000444089209850062616169452667236328125 a+b = 1.9000000000000001332267629550187848508358001708984375 a*b = 0.8800000000000001199040866595169103100567462588676208086428264139311483660321755451150238513946533203125 java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
正確使用方法:orm
public void testBigDecimalNormal(){ BigDecimal a = new BigDecimal("1.1"); BigDecimal b = new BigDecimal("0.8"); System.out.println("a-b = "+(a.subtract(b))); System.out.println("a+b = "+(a.add(b))); System.out.println("a*b = "+(a.multiply(b))); System.out.println("a/b = "+(a.divide(b))); }
結果以下:
a-b = 0.3 a+b = 1.9 a*b = 0.88 a/b = 1.375
經過上面的例子,咱們能夠清晰的看出。除了正確使用BigDecimal類型外,其他的在計算過程當中,均損失精度。所以咱們能夠得出如下結論:
在須要精度計算數值時,不該該使用float,double 類型,進行計算。
BigDecimal 應該使用 String 構造函數,禁止使用double構造函數。
其實,在使用BigDecimal過程,也有許多須要注意的細節。
科學計數法問題
@Test public void testBigDecimalResult(){ BigDecimal b = new BigDecimal("0.0000001"); System.out.println(b.toString()); System.out.println(b.toPlainString()); }
執行結果:
1E-7 0.0000001
結論:當 BigDecimal的值 小於必定值時(測試時發現:小於等於0.0000001)時,則會被記爲科學計數法。可使用 toPlainString()
方法顯示原來的值。
2. 去除多餘的 0
@Test public void testBigDecimalStripZeros(){ BigDecimal b = new BigDecimal("0.000000100000000"); System.out.println(b.stripTrailingZeros().toString()); System.out.println(b.stripTrailingZeros().toPlainString()); }
使用場景:去除多餘的0,當金額有小數位限制時,使用該方法可以去除掉無效的0,從而達到自動修復無效參數的目的。
結論:stripTrailingZeros() 方法的本質是去除掉多餘的0,其返回數據類型是BigDecimal,一樣的在使用時須要注意科學技術法的問題。
3. 保留小數位
@Test public void testBigDecimalStripZeros(){ BigDecimal d = new BigDecimal("1.2222"); d.setScale(2); System.out.println(d.toPlainString()); }
運行結果:
java.lang.ArithmeticException: Rounding necessary
緣由:在setScale()方法中的roundingMode屬性設置爲了ROUND_UNNECESSARY,代碼以下:
public BigDecimal setScale(int newScale) { return setScale(newScale,); }
而在:
java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4179)
中ROUND_UNNECESSARY 類型偏偏會拋出異常。代碼顯示以下:
private static boolean commonNeedIncrement(int roundingMode, int qsign, int cmpFracHalf, boolean oddQuot) { switch(roundingMode) { case ROUND_UNNECESSARY: throw new ArithmeticException("Rounding necessary"); case ROUND_UP: // Away from zero return true; ...
經過上面的例子,如今咱們已經知道了BigDecimal的一些使用細節。其實呀,這些都是血淋淋的教訓換來的經驗,每個小細節對應的都是一個個事故,記憶猶新。這裏推薦你們都抽時間看看《Java開發手冊》,就能避免掉不少坑。
上面的問題,在《Java開發手冊》中一樣有寫到:
【強制】爲了防止精度損失,禁止使用構造方法BigDecimal(double)的方式把double值轉化爲BigDecimal對象。
說明:BigDecimal(double) 存在精度損失風險,在精確計算或值比較的場景中可能會致使業務邏輯異常。如:
BigDecimal g = new BigDecimal(0.1f); 實際的存儲值爲:0.10000000149正例:優先推薦入參爲String 的構造函數,或使用BigDecimal的valueOf方法。
相關閱讀:
掃碼關注,一塊兒進步
我的博客: http://www.andyqian.com