Java BigDecimal使用指南


提起BigDecimal,相信大家都使用過,之所以總結這篇呢,是因為最近發現項目中使用的不是太規范,在某些場景下甚至出現代碼拋出異常的情況,

所以就總結了這篇,希望大家在使用時,可以少踩一些坑。

1. 基本運算

1.1 加法

BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = new BigDecimal("11.12");

BigDecimal number3 = number1.add(number2);
System.out.println("number1 add number2 = " + number3);

輸出結果:

number1 add number2 = 100.00

1.2 減法

BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = new BigDecimal("11.12");

BigDecimal number3 = number1.subtract(number2);
System.out.println("number1 subtract number2 = " + number3);

輸出結果:

number1 subtract number2 = 77.76

1.3 乘法

BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = new BigDecimal("11.12");

BigDecimal number3 = number1.multiply(number2);
System.out.println("number1 multiply number2 = " + number3);

輸出結果:

number1 multiply number2 = 988.3456

1.4 除法

BigDecimal number1 = new BigDecimal("88");
BigDecimal number2 = new BigDecimal("11");

BigDecimal number3 = number1.divide(number2);
System.out.println("number1 divide number2 = " + number3);

輸出結果:

number1 divide number2 = 8

因為上面2個數可以整除,所以這么用沒有問題,不過一但不能被整除,這么用就會有潛在的風險,會拋出java.lang.ArithmeticException異常,所以強烈建議像下面這樣使用

BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = new BigDecimal("11.12");

BigDecimal number3 = number1.divide(number2, 2, RoundingMode.HALF_UP);
System.out.println("number1 divide number2 = " + number3);

輸出結果:

number1 divide number2 = 7.99

此時使用的divide()方法源碼如下所示:

public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) {
    return divide(divisor, scale, roundingMode.oldMode);
}

這里的scale指的是要保留的小數位數,我們傳的是2,即保留2位小數。

這里的roundingMode指的是舍入模式,我們這里傳的是RoundingMode.HALF_UP,即經常使用的四舍五入模式。

1.5 保留小數位數

如果我們想對BigDecimal類型保留小數位數,可以使用setScale()方法,使用方法如下所示:

BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = new BigDecimal("11.12");

BigDecimal number3 = number1.multiply(number2);
System.out.println("number1 multiply number2 = " + number3);

// 保留3位小數,四舍五入
BigDecimal number4 = number3.setScale(3, RoundingMode.HALF_UP);
System.out.println("number3 setScale = " + number4);

輸出結果:

number1 multiply number2 = 988.3456

number3 setScale = 988.346

1.6 比較大小

BigDecimal比較大小,可以使用compareTo()方法,使用方法如下所示:

BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = new BigDecimal("11.11");
BigDecimal number3 = new BigDecimal("99.99");
BigDecimal number4 = new BigDecimal("88.88");

System.out.println("number1 compareTo number2 = " + number1.compareTo(number2));
System.out.println("number1 compareTo number3 = " + number1.compareTo(number3));
System.out.println("number1 compareTo number4 = " + number1.compareTo(number4));

輸出結果:

number1 compareTo number2 = 1

number1 compareTo number3 = -1

number1 compareTo number4 = 0

由輸出結果可以看出:

當number1小於number2時,返回-1,

當number1等於number2時,返回0,

當number1大於number2時,返回1。

2. 踩坑總結

2.1 NullPointerException異常

在使用BigDecimal類型進行計算時,比如上面提到的加、減、乘、除、比較大小時,一定要保證參與計算的兩個值不能為空,否則會拋出java.lang.NullPointerException異常。

比如下面的2段代碼,都會拋出異常:

BigDecimal number1 = null;
BigDecimal number2 = new BigDecimal("11.12");

BigDecimal number3 = number1.add(number2);
System.out.println("number1 add number2 = " + number3);
BigDecimal number1 = new BigDecimal("88.88");
BigDecimal number2 = null;

BigDecimal number3 = number1.add(number2);
System.out.println("number1 add number2 = " + number3);

拋出的異常如下圖所示:

2.2 ArithmeticException異常

一次在使用BigDecimaldivide方法時,拋出java.lang.ArithmeticException異常,錯誤代碼如下所示:

// 含稅金額
BigDecimal inclusiveTaxAmount = new BigDecimal("1000");
// 稅率
BigDecimal taxRate = new BigDecimal("0.13");
// 不含稅金額 = 含稅金額 / (1+稅率)
BigDecimal exclusiveTaxAmount = inclusiveTaxAmount.divide(BigDecimal.ONE.add(taxRate));

System.out.println(exclusiveTaxAmount);

運行時拋出以下異常:

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

報錯原因是因為無法整除,導致結果是無限循環小數:

解決方案是指定下舍入模式,比如我們最常用的四舍五入模式:

// 不含稅金額 = 含稅金額 / (1+稅率)
BigDecimal exclusiveTaxAmount = inclusiveTaxAmount.divide(BigDecimal.ONE.add(taxRate),RoundingMode.HALF_UP);

此時不再報錯,輸出結果為:

885

但這里我的需求是保留2位小數,四舍五入,因此代碼應該是下面這樣的:

// 不含稅金額 = 含稅金額 / (1+稅率)
BigDecimal exclusiveTaxAmount = inclusiveTaxAmount.divide(BigDecimal.ONE.add(taxRate), 2, RoundingMode.HALF_UP);

此時的輸出結果為:

884.96

如果你的IDEA裝了阿里巴巴代碼規范插件,如果不指定RoundingMode,會有下面這樣的提示:

2.3 BigDecimal轉String,科學計數法問題

結論:將BigDecimal轉換為String,推薦使用toPlainString(),而不是toString()

可能你的寫法是下面這樣的:

BigDecimal amount = new BigDecimal("3450.67");
System.out.println(amount.toString());

輸出結果:

3450.67

大部分情況下,這么使用並沒有問題,不過在某些場景下,這么使用就容易踩坑,比如下面的3個示例:

System.out.println( new BigDecimal("0.000000000000").toString());

輸出結果:

0E-12

BigDecimal bigDecimal = new BigDecimal("1E+11");
System.out.println(bigDecimal.toString());

輸出結果:

1E+11

還有個更為常用的場景:抹零,也容易踩坑,比如下面所示的代碼,預期的輸出結果是3550,但實際上並不是:

BigDecimal bigDecimal = new BigDecimal("3550.00");
System.out.println(bigDecimal.stripTrailingZeros().toString());

輸出結果:

3.55E+3

使用toPlainString()方法可以避免這個問題,如下所示:

System.out.println( new BigDecimal("0.000000000000").toPlainString());
System.out.println( new BigDecimal("1E+11").toPlainString());
System.out.println(new BigDecimal("3550.00").stripTrailingZeros().toPlainString());

輸出結果:

0.000000000000

100000000000

3550

其實,BigDecimal提供了3個轉換為String的方法,分別為:

  1. toString() 某些場景下使用科學計數法

    上面的踩坑就是因為toString()在某些場景下使用科學計數法的原因

  2. toPlainString() 不使用任何計數法

  3. toEngineeringString() 某些場景下使用工程計數法

這里簡單提下科學計數法和工程計數法的區別:

科學記數法,是將數字表示成10的冪的倍數的形式。

工程記數法,是在科學記數法基礎上,將10的冪限制為3的倍數。

舉例:

原始值 科學技術法 工程計數法
2700 2.7 x 10³ 2.7 x 10³
27000 2.7 x 10⁴ 27 x 10³
270000 2.7 x 10⁵ 270 x 10³
2700000 2.7 x 10⁶ 2.7 x 10⁶

示例代碼:

BigDecimal bigDecimal = new BigDecimal("270000.00").stripTrailingZeros();
System.out.println(bigDecimal.toString());
System.out.println(bigDecimal.toPlainString());
System.out.println(bigDecimal.toEngineeringString());

輸出結果:

2.7E+5

270000

270E+3

3. 使用規范

盡量不要在項目中使用new BigDecimal("0"),而是使用BigDecimal提供的常量BigDecimal.ZERO

BigDecimal zero = BigDecimal.ZERO;
BigDecimal one = BigDecimal.ONE;
BigDecimal ten = BigDecimal.TEN;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM