BigDecimal詳細解析

簡介

​ BigDecimal 由任意精度的整數非標度值 和32 位的整數標度 (scale) 組成。如果爲零或正數,則標度是小數點後的位數。如果爲負數,則將該數的非標度值乘以 10 的負scale 次冪。因此,BigDecimal表示的數值是(unscaledValue × 10-scale)。

先運行一套代碼

public static void main(String[] args)
    {
        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);
    }

輸出效果

0.30000000000000004
0.19999999999999998
0.020000000000000004
2.9999999999999996

原因

我們的計算機是二進制的。浮點數沒有辦法是用二進制進行精確表示。我們的CPU表示浮點數由兩個部分組成:指數和尾數,這樣的表示方法一般都會失去一定的精確度,有些浮點數運算也會產生一定的誤差。如:2.4的二進制表示並非就是精確的2.4。反而最爲接近的二進制表示是 2.3999999999999999。浮點數的值實際上是由一個特定的數學公式計算得到的。

構造方法

1.public BigDecimal(double val) 將double表示形式轉換爲BigDecimal *不建議使用

2.public BigDecimal(int val)  將int表示形式轉換成BigDecimal

3.public BigDecimal(String val)  將String表示形式轉換成BigDecimal

示例

BigDecimal bigDecimal = new BigDecimal(2);
BigDecimal bDouble = new BigDecimal(2.4);
BigDecimal bString = new BigDecimal("2.4");
System.out.println("bigDecimal=" + bigDecimal);
System.out.println("bDouble=" + bDouble);
System.out.println("bString=" + bString);

輸出結果

bigDecimal=2
bDouble=2.399999999999999911182158029987476766109466552734375
bString=2.4

JDK的描述(原因)

1、參數類型爲double的構造方法的結果有一定的不可預知性。有人可能認爲在Java中寫入newBigDecimal(0.1)所創建的BigDecimal正好等於 0.1(非標度值 1,其標度爲 1),但是它實際上等於0.1000000000000000055511151231257827021181583404541015625。這是因爲0.1無法準確地表示爲 double(或者說對於該情況,不能表示爲任何有限長度的二進制小數)。這樣,傳入到構造方法的值不會正好等於 0.1(雖然表面上等於該值)。

​ 2、另一方面,String 構造方法是完全可預知的:寫入 newBigDecimal(“0.1”) 將創建一個 BigDecimal,它正好等於預期的 0.1。因此,比較而言,通常建議優先使用String構造方法

如果是確定用double,則可以用Double.toString(2.4),如下代碼

BigDecimal bDouble1 = BigDecimal.valueOf(2.4);
BigDecimal bDouble2 = new BigDecimal(Double.toString(2.4));

System.out.println("bDouble1=" + bDouble1);
System.out.println("bDouble2=" + bDouble2);

輸出結果

bDouble1=2.4
bDouble2=2.4

幾種常用函數

  1. 加:BigDecimal add(BigDecimal augend)

  2. 減:BigDecimal subtract(BigDecimal subtrahend)

  3. 乘:BigDecimal multiply(BigDecimal multiplicand)

  4. 除法:BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

    (BigDecimal divisor 除數, int scale 精確小數位, int roundingMode 舍入模式)

  5. 絕對值:BigDecimal abs()

例子代碼

BigDecimal num1 = new BigDecimal(2.4);
BigDecimal num2 = new BigDecimal(2);
BigDecimal num3 = new BigDecimal(-2);
//儘量用字符串的形式初始化
BigDecimal num12 = new BigDecimal("2.4");
BigDecimal num22 = new BigDecimal("2");
BigDecimal num32 = new BigDecimal("-2");

//加法
BigDecimal result1 = num1.add(num2);
BigDecimal result12 = num12.add(num22);
//減法
BigDecimal result2 = num1.subtract(num2);
BigDecimal result22 = num12.subtract(num22);
//乘法
BigDecimal result3 = num1.multiply(num2);
BigDecimal result32 = num12.multiply(num22);
//絕對值
BigDecimal result4 = num3.abs();
BigDecimal result42 = num32.abs();
//除法
BigDecimal result5 = num2.divide(num1,20,BigDecimal.ROUND_HALF_UP);
BigDecimal result52 = num22.divide(num12,20,BigDecimal.ROUND_HALF_UP);

System.out.println("加法用value結果 result1:"+result1);
System.out.println("加法用string結果 result12:"+result12);

System.out.println("減法value結果:"+result2);
System.out.println("減法用string結果:"+result22);

System.out.println("乘法用value結果 result3:"+result3);
System.out.println("乘法用string結果 result32:"+result32);

System.out.println("絕對值用value結果 result4:"+result4);
System.out.println("絕對值用string結果 result42:"+result42);

System.out.println("除法用value結果 result5:"+result5);
System.out.println("除法用string結果 result52:"+result52);

輸出結果

加法用value結果 result1:4.399999999999999911182158029987476766109466552734375
加法用string結果 result12:4.4
減法value結果:0.399999999999999911182158029987476766109466552734375
減法用string結果:0.4
乘法用value結果 result3:4.799999999999999822364316059974953532218933105468750
乘法用string結果 result32:4.8
絕對值用value結果 result4:2
絕對值用string結果 result42:2
除法用value結果 result5:0.83333333333333336417
除法用string結果 result52:0.83333333333333333333

除法的八種舍入模式

1、ROUND_UP

舍入遠離零的舍入模式。

在丟棄非零部分之前始終增加數字(始終對非零捨棄部分前面的數字加1)。

注意,此舍入模式始終不會減少計算值的大小。

2、ROUND_DOWN

接近零的舍入模式。

在丟棄某部分之前始終不增加數字(從不對捨棄部分前面的數字加1,即截短)。

注意,此舍入模式始終不會增加計算值的大小。

3、ROUND_CEILING

接近正無窮大的舍入模式。

如果 BigDecimal 爲正,則舍入行爲與 ROUND_UP 相同;

如果爲負,則舍入行爲與 ROUND_DOWN 相同。

注意,此舍入模式始終不會減少計算值。

4、ROUND_FLOOR

接近負無窮大的舍入模式。

如果 BigDecimal 爲正,則舍入行爲與 ROUND_DOWN 相同;

如果爲負,則舍入行爲與 ROUND_UP 相同。

注意,此舍入模式始終不會增加計算值。

5、ROUND_HALF_UP

向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則爲向上舍入的舍入模式。

如果捨棄部分 >= 0.5,則舍入行爲與 ROUND_UP 相同;否則舍入行爲與 ROUND_DOWN 相同。

注意,這是我們大多數人在小學時就學過的舍入模式(四捨五入)。

6、ROUND_HALF_DOWN

向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則爲上舍入的舍入模式。

如果捨棄部分 > 0.5,則舍入行爲與 ROUND_UP 相同;否則舍入行爲與 ROUND_DOWN 相同(五舍六入)。

7、ROUND_HALF_EVEN

向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。

如果捨棄部分左邊的數字爲奇數,則舍入行爲與 ROUND_HALF_UP 相同;

如果爲偶數,則舍入行爲與 ROUND_HALF_DOWN 相同。

注意,在重複進行一系列計算時,此舍入模式可以將累加錯誤減到最小。

此舍入模式也稱爲“銀行家舍入法”,主要在美國使用。四捨六入,五分兩種情況。

如果前一位爲奇數,則入位,否則捨去。

以下例子爲保留小數點1位,那麼這種舍入方式下的結果。

1.15>1.2 1.25>1.2

8、ROUND_UNNECESSARY

斷言請求的操作具有精確的結果,因此不需要舍入。

示例

BigDecimal num1 = new BigDecimal("2.4");
BigDecimal num2 = new BigDecimal("2");

BigDecimal result1 = num2.divide(num1,3,BigDecimal.ROUND_UP);
BigDecimal result2 = num2.divide(num1,3,BigDecimal.ROUND_DOWN);
BigDecimal result3 = num2.divide(num1,3,BigDecimal.ROUND_CEILING);
BigDecimal result4 = num2.divide(num1,3,BigDecimal.ROUND_FLOOR);
BigDecimal result5 = num2.divide(num1,3,BigDecimal.ROUND_HALF_UP);
BigDecimal result6 = num2.divide(num1,3,BigDecimal.ROUND_HALF_DOWN);
BigDecimal result7 = num2.divide(num1,3,BigDecimal.ROUND_HALF_EVEN);

System.out.println("BigDecimal.ROUND_UP result1:"+result1);
System.out.println("BigDecimal.ROUND_DOWN result2:"+result2);
System.out.println("BigDecimal.ROUND_CEILING result3:"+result3);
System.out.println("BigDecimal.ROUND_FLOOR result4:"+result4);
System.out.println("BigDecimal.ROUND_HALF_UP result5:"+result5);
System.out.println("BigDecimal.ROUND_HALF_DOWN result6:"+result6);
System.out.println("BigDecimal.ROUND_HALF_EVEN result7:"+result7);

輸出結果

BigDecimal.ROUND_UP result1:0.834
BigDecimal.ROUND_DOWN result2:0.833
BigDecimal.ROUND_CEILING result3:0.834
BigDecimal.ROUND_FLOOR result4:0.833
BigDecimal.ROUND_HALF_UP result5:0.833
BigDecimal.ROUND_HALF_DOWN result6:0.833
BigDecimal.ROUND_HALF_EVEN result7:0.833

這裏要注意ROUND_UNNECESSARY如果相除是無限小數會報異常

BigDecimal num1 = new BigDecimal("2.4");
BigDecimal num2 = new BigDecimal("2");
BigDecimal result8 = num2.divide(num1,2,BigDecimal.ROUND_UNNECESSARY);
System.out.println("BigDecimal.ROUND_UNNECESSARY result8:"+result8);

輸出結果

Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
	at java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4179)
	at java.math.BigDecimal.needIncrement(BigDecimal.java:4235)
	at java.math.BigDecimal.divideAndRound(BigDecimal.java:4143)
	at java.math.BigDecimal.divide(BigDecimal.java:5214)
	at java.math.BigDecimal.divide(BigDecimal.java:1564)
	at fking.Demo5.main(Demo5.java:35)

如果改成

BigDecimal num1 = new BigDecimal("2");
BigDecimal num2 = new BigDecimal("5");
BigDecimal result8 = num2.divide(num1,2,BigDecimal.ROUND_UNNECESSARY);
System.out.println("BigDecimal.ROUND_UNNECESSARY result8:"+result8);

輸出結果

BigDecimal.ROUND_UNNECESSARY result8:2.50

總結結論

(1)商業計算使用BigDecimal。

​ (2)儘量使用參數類型爲String的構造函數。

​ (3) BigDecimal都是不可變的(immutable)的,在進行每一步運算時,都會產生一個新的對象,所以在做加減乘除運算時千萬要保存操作後的值。

如果大家對java架構相關感興趣,可以關注下面公衆號,會持續更新java基礎面試題, netty, spring boot,spring cloud等系列文章,一系列乾貨隨時送達, 超神之路從此展開, BTAJ不再是夢想!

架構殿堂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章