1、BigDecimal簡介
借用《Effactive Java》書中的一句話,float和double類型設計的主要目标是為了科學計算和工程計算。它們主要用于執行二進制浮點運算,這是為了在廣域數值範圍上提供較為精确的快速近似計算而精心設計的。
罷特,它們沒有提供完全精确的計算結果,是以不應該被用于要求精确結果的場合。但是,在商業計算中往往要求結果精确,這時候BigDecimal就派上大用場啦。
Java在java.math包中提供的API類BigDecimal 由
任意精度的整數非标度值
和
32 位的整數标度 (scale)
組成。如果為零或正數,則标度是小數點後的位數。如果為負數,則将該數的非标度值乘以 10 的負scale 次幂。是以,BigDecimal表示的數值是(unscaledValue × 10 -scale)。
BigDecimal用來對超過16位有效位的數進行精确的運算。雙精度浮點型變量double可以處理16位有效數,但在實際應用中,可能需要對更大或者更小的數進行運算和處理。一般情況下,對于那些不需要準确計算精度的數字,我們可以直接使用Float和Double處理,但是Double.valueOf(String) 和Float.valueOf(String)會丢失精度。是以在開發中,如果你所做的業務跟财務相關需要精确計算的結果,那必須使用BigDecimal類來操作啦!
BigDecimal
所建立的是
對象
,我們不能使用傳統的
+、-、*、/
等算術運算符直接對其對象進行數學運算,而必須調用其相對應的方法。方法中的參數也必須是BigDecimal的對象。構造器是類的特殊方法,專門用來建立對象,特别是帶有參數的對象。
2、構造器及方法描述
2.1、常用構造器
- BigDecimal(int) 建立一個具有參數所指定整數值的對象。
- BigDecimal(double) 建立一個具有參數所指定雙精度值的對象。 【不推薦使用】
- BigDecimal(long) 建立一個具有參數所指定長整數值的對象。
- BigDecimal(String) 建立一個具有參數所指定以字元串表示的數值的對象。【推薦使用】
2.1、常用方法
- add(BigDecimal) BigDecimal對象中的值相加,然後傳回這個對象。
- subtract(BigDecimal) BigDecimal對象中的值相減,然後傳回這個對象。
- multiply(BigDecimal) BigDecimal對象中的值相乘,然後傳回這個對象。
- divide(BigDecimal) BigDecimal對象中的值相除,然後傳回這個對象。
- toString() 将BigDecimal對象的數值轉換成字元串。
- doubleValue() 将BigDecimal對象中的值以雙精度數傳回。
- floatValue() 将BigDecimal對象中的值以單精度數傳回。
- longValue() 将BigDecimal對象中的值以長整數傳回。
- intValue() 将BigDecimal對象中的值以整數傳回。
2.3、解釋不推薦使用的構造器
1、為什麼不推薦使用BigDecimal(double),而推薦使用BigDecimal(String)?
@Test
public void test1(){
//BigDecimal(Double)
BigDecimal doubleStr = new BigDecimal(21.111111);
//BigDecimal(String)
BigDecimal intStr = new BigDecimal("21.111111");
System.out.println(doubleStr);
System.out.println(intStr);
}
從上述運作結果就可以看出,使用double類型作為構造源,會有計算問題!!!
JDK的描述原因:
- 參數類型為double的構造方法的結果有一定的不可預知性。有人可能認為在Java中寫入newBigDecimal(0.1)所建立的BigDecimal正好等于 0.1(非标度值 1,其标度為 1),但是它實際上等于0.1000000000000000055511151231257827021181583404541015625。這是因為0.1無法準确地表示為 double(或者說對于該情況,不能表示為任何有限長度的二進制小數)。這樣的話,傳入到構造方法的值不會正好等于 0.1(雖然表面上等于該值,但是在JDK内部參與運算的時候不是0.1,這樣就造成了一定的計算誤差)。
- 另一方面,String 構造方法是完全可預知的:寫入 newBigDecimal("0.1") 将建立一個 BigDecimal,它正好等于預期的 0.1。是以,比較而言,
。通常建議優先使用String構造方法
2、當double必須用作BigDecimal的構造源時,可以使用
Double.toString(double)轉成String
,然後使用String構造方法,或使用
BigDecimal的靜态方法valueOf()
,如下:
@Test
public void test2(){
BigDecimal doubleStr = new BigDecimal(Double.toString(21.222222));
BigDecimal intStr = BigDecimal.valueOf(21.222222);
System.out.println(doubleStr);
System.out.println(intStr);
}
3、BigDecimal的加減乘除應用
3.1、普通的+、-、*、/
@Test
public void test3(){
System.out.println(0.2 + 0.1);
System.out.println(0.3 - 0.1);
System.out.println(0.2 * 0.1);
System.out.println(0.3 / 0.1);
}
出現上述結果刺激吧!那為什麼會出現上述請款呢?
- 因為不論是float 還是double都是浮點數,計算機隻認識二進制,浮點數會失去一定的精确度。
- 詳解:十進制值通常沒有完全相同的二進制表示形式;十進制數的二進制表示形式可能不精确,隻能無限接近于精确值。
在真正的開發項目中,我們不可能讓這種情況出現,特别是
金融财務項目
,因為涉及金額的計算都必須十分精确,你想想,如果你的支付寶賬戶餘額顯示0.99999999999998,那是一種怎麼樣的體驗?反正我是難受的扒不出來!!!
3.2、BigDecimal的+、-、*、/
@Test
public void test4(){
BigDecimal num1 = new BigDecimal("0.3");
BigDecimal num2 = new BigDecimal("0.1");
System.out.println("加:" + num1.add(num2));
System.out.println("減:" + num1.subtract(num2));
System.out.println("乘:" + num1.multiply(num2));
System.out.println("除:" + num1.divide(num2));
}
3.3、特别注意BigDecimal的除法不能整除的情況(需要進行四舍五入)
如果出現以下異常錯誤:
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
原因:
如果進行除法運算的時候,,這個時候就會報java.lang.ArithmeticException: ,這邊我們要避免這個錯誤産生,在進行除法運算的時候,針對可能出現的小數産生的計算,在divide方法中除數後面必須要多傳兩個參數:divide(
結果不能整除,帶有有餘數
,
除數
保留小數點後幾位小數
)。
舍入模式
舍入模式:
- ROUND_UP :向遠離0的方向舍入
- ROUND_DOWN :向零方向舍入
- ROUND_CEILING :向正無窮方向舍入
- ROUND_FLOOR :向負無窮方向舍入
- ROUND_HALF_UP :向(距離)最近的一邊舍入,如果兩邊(的距離)是相等時,向上舍入, 1.55保留一位小數結果為1.6,也就是我們常說的“
”四舍五入
- ROUND_HALF_DOWN :向(距離)最近的一邊舍入,如果兩邊(的距離)是相等時,向下舍入, 例如1.55 保留一位小數結果為1.5
- ROUND_HALF_EVEN :向(距離)最近的一邊舍入,如果兩邊(的距離)是相等時,如果保留位數是奇數,使用ROUND_HALF_UP,如果是偶數,使用ROUND_HALF_DOWN
- ROUND_UNNECESSARY :計算結果是精确的,不需要舍入模式
案例1:
@Test
public void test5(){
double aDouble = 1.233;
float aFloat = 3.3f;
BigDecimal num3 = new BigDecimal(Double.toString(aDouble));
BigDecimal num4 = new BigDecimal(Float.toString(aFloat));
System.out.println("乘法:" + num3.multiply(num4));
//System.out.println("除法:" + num3.divide(num4)); //會報錯
System.out.println("除法,四舍五入後,保留兩位小數:" + num3.divide(num4,2,BigDecimal.ROUND_HALF_UP));
}
案例2:
@Test
public void test6(){
BigDecimal intStr = BigDecimal.valueOf(10);
BigDecimal doubleStr = new BigDecimal(Double.toString(3));
//後面代表的是舍入模式的值
System.out.println("ROUND_UP:" + intStr.divide(doubleStr,2,BigDecimal.ROUND_UP));//0
System.out.println("ROUND_DOWN:" + intStr.divide(doubleStr,2,BigDecimal.ROUND_DOWN));//1
System.out.println("ROUND_CEILING:" + intStr.divide(doubleStr,2,BigDecimal.ROUND_CEILING)); //2
System.out.println("ROUND_FLOOR:" + intStr.divide(doubleStr,2,BigDecimal.ROUND_FLOOR));//3
System.out.println("ROUND_HALF_UP:" + intStr.divide(doubleStr,2,BigDecimal.ROUND_HALF_UP));//4
System.out.println("ROUND_HALF_DOWN:" + intStr.divide(doubleStr,2,BigDecimal.ROUND_HALF_DOWN));//5
System.out.println("ROUND_HALF_EVEN:" + intStr.divide(doubleStr,2,BigDecimal.ROUND_HALF_EVEN));//6
}
運作結果:
注意:這個divide方法有兩個重載的方法,一個是傳
兩個參數的
,一個是傳
三個參數
的。
//兩個參數的(隻需傳入舍入模式值,但是保留位數按舍入模式的預設位數)
public BigDecimal divide(BigDecimal divisor, int roundingMode) {
return this.divide(divisor, scale, roundingMode);
}
//三個參數的
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) {
if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY)
throw new IllegalArgumentException("Invalid rounding mode");
if (this.intCompact != INFLATED) {
if ((divisor.intCompact != INFLATED)) {
return divide(this.intCompact, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
} else {
return divide(this.intCompact, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
}
} else {
if ((divisor.intCompact != INFLATED)) {
return divide(this.intVal, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
} else {
return divide(this.intVal, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
}
}
}
需要對BigDecimal進行截斷和四舍五入可用setScale方法,例如:
@Test
public void test7(){
BigDecimal num = new BigDecimal("10.254");
System.out.println(num.setScale(2,BigDecimal.ROUND_HALF_UP ));//保四舍五入,留兩位小數
}