全面的BigDecimal詳解

                                           BigDecimal詳解

 

       在平時學習中,大家很少接觸到BigDecimal,有些朋友還是第一次接觸到這個類,那麼就先談談BigDecimal是什麼,並且BigDecimal用處是什麼,在什麼場合使用,BigDecimal如何去使用。

1 初識BigDecimal

 

1.1 BigDecimal使用背景

       在java運算某些數字時,會出現在千萬甚至億萬的小數位上出錯,這些都是在浮點運算的時候纔會出現的一些小誤差的結果。也就是精度缺失。雙精度浮點型變量double可以處理16位有效數。

      在實際應用中,需要對更大或者更小的數進行運算和處理。float和double只能用來做科學計算或者是工程計算,在商業計算中要用java.math.BigDecimal。尤其是有關金融行業的開發,有關數字的計算都不會去使用float和double。在金錢方面這個就尤爲重要,試想開發使用float或者double類型導致精度缺失,之後在此數上*10000000000000,這時會發現自己錢少了?或者多了?這個是客戶所不願看見的。基於此種情況就需要使用java.math.BigDecimal。

1.2 BigDecimal用處

       Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數進行精確的運算。BigDecimal的使用可以解決浮點運算精度丟失的問題。

首先稍微理解下爲什麼會精度丟失:

       原因在於我們的計算機是二進制的。浮點數沒有辦法是用二進制進行精確表示。我們的CPU表示浮點數由兩個部分組成:指數和尾數,這樣的表示方法一般都會失去一定的精確度,有些浮點數運算也會產生一定的誤差。

       博主這裏推薦一篇文章可以幫助理解精度缺失問題:https://blog.csdn.net/aosica321/article/details/53536928

那爲什麼BigDecimal可以解決精度丟失的問題呢?

      BigDecimal所創建的是對象,大家也知道我們所創建的基本數據類型和引用數據類型在內存中存儲是不一樣的,基本數據類型存儲在棧中,引用數據類型存儲在堆中。

      在堆中存儲是不能直接進行操作,因此BigDecimal不能使用傳統的+、-、*、/等算術運算符直接對其對象進行數學運算,而必須調用其相對應的方法。方法中的參數也必須是BigDecimal的對象。構造器是類的特殊方法,專門用來創建對象,特別是帶有參數的對象。

2 BigDecimal的使用

 

2.1 構建BigDecimal

BigDecimal一共有4個構造方法:

BigDecimal(int) 創建一個具有參數所指定整數值的對象。

BigDecimal(double) 創建一個具有參數所指定雙精度值的對象。(不建議採用)

BigDecimal(long) 創建一個具有參數所指定長整數值的對象。

BigDecimal(String) 創建一個具有參數所指定以字符串表示的數值的對象

爲什麼不建議採用第二種構造方法?如下代碼:

   public static void main(String[] args) {
        	BigDecimal bigDecimal = new BigDecimal(2);
        	BigDecimal bDouble = new BigDecimal(2.3);
        	BigDecimal bString = new BigDecimal("3.2");
        	System.out.println("bigDecimal:"+bigDecimal);
        	System.out.println("bDouble:"+bDouble);
        	System.out.println("bString:"+bString);
         }

運行結果:

bigDecimal:2
bDouble:2.29999999999999982236431605997495353221893310546875
bString:3.2

      原因: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必須用作BigDecimal的源時,請使用Double.toString(double)轉成String,然後使用String構造方法,或使用BigDecimal的靜態方法valueOf,如下:

        public static void main(String[] args) {
     
        	BigDecimal bDouble = new BigDecimal(2.3);
        	BigDecimal bString = new BigDecimal("2.3");
        	BigDecimal bDoubleToString = new BigDecimal(Double.toString(2.3));
        
        	System.out.println("bDouble:"+bDouble);
        	System.out.println("bString:"+bString);
        	System.out.println("bDoubleToString:"+bDoubleToString);
         }

運行結果:

bDouble:2.29999999999999982236431605997495353221893310546875
bString:2.3
bDoubleToString:2.3

      由於NumberFormat類的format()方法可以使用BigDecimal對象作爲其參數,可以利用BigDecimal對超出16位有效數字的貨幣值,百分值,以及一般數值進行格式化控制。 以利用BigDecimal對貨幣和百分比格式化爲例。首先,創建BigDecimal對象,進行BigDecimal的算術運算後,分別建立對貨幣和百分比格式化的引用,最後利用BigDecimal對象作爲format()方法的參數,輸出其格式化的貨幣值和百分比

代碼樣例:

BigDecimal bigLoanAmount = new BigDecimal("具體數值");   //創建BigDecimal對象

BigDecimal 

bigInterestRate = new BigDecimal("具體數值");


BigDecimal bigInterest = bigLoanAmount.multiply(bigInterestRate);//BigDecimal運算

NumberFormat 

currency = NumberFormat.getCurrencyInstance();    //建立貨幣格式化引用


NumberFormat percent = NumberFormat.getPercentInstance();     //建立百分比格式化用


percent.setMaximumFractionDigits(3);               //百分比小數點最多3位


//利用BigDecimal對象作爲參數在format()中調用貨幣和百分比格式化


System.out.println("Loan amount:\t" + currency.format(bigLoanAmount));


System.out.println("Interest rate:\t" + percent.format(bigInterestRate));


System.out.println("Interest:\t" + currency.format(bigInterest));

輸出結果:

Loan amount:  ¥129,876,534,219,876,523.12

Interest rate: 8.765%

Interest:  

¥11,384,239,549,149,661.69

常見用法:

初始化 BigDecimal a= new BigDecimal("1.35");


對數值取值:

1.a.setScale(1,BigDecimal.ROUND_DOWN);


取一位小數,直接刪除後面多餘位數,故取值1.3.

2.a.setScale(1,BigDecimal.ROUND_UP);


取一位小數,刪除後面位數,進一位,故取值1.4.

3.a.setScale(1,BigDecimal.ROUND_HALF_UP);


取一位小數,四捨五入,故取值1.4.

4.a.setScale(1,BigDecimal.ROUND_HALF_DOWN);


取一位小數,五舍六入,故取值1.3.

2.2 Bigdecimal運算

Bigdecimal的類中已經定義好一些運算函數供我們調用使用:
       加法 add()函數

      減法subtract()函數

      乘法multipy()函數

      除法divide()函數

      絕對值abs()函數

我這裏承接上面初始化Bigdecimal分別用string和數進行運算對比

public static void main(String[] args) {
            BigDecimal num1 = new BigDecimal(0.005);
            BigDecimal num2 = new BigDecimal(1000);
            BigDecimal num3 = new BigDecimal(-1000);
            //儘量用字符串的形式初始化
            BigDecimal num12 = new BigDecimal("0.005");
            BigDecimal num22 = new BigDecimal("1000");
            BigDecimal num32 = new BigDecimal("-1000");
     
            //加法
            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);
            System.out.println("加法用string結果:"+result12);
     
            System.out.println("減法value結果:"+result2);
            System.out.println("減法用string結果:"+result22);
     
            System.out.println("乘法用value結果:"+result3);
            System.out.println("乘法用string結果:"+result32);
     
            System.out.println("絕對值用value結果:"+result4);
            System.out.println("絕對值用string結果:"+result42);
     
            System.out.println("除法用value結果:"+result5);
            System.out.println("除法用string結果:"+result52);
   }

我把result全部輸出可以看到結果

加法用value結果:1000.005000000000000000104083408558608425664715468883514404296875
加法用string結果:1000.005
減法value結果:-999.994999999999999999895916591441391574335284531116485595703125
減法用string結果:-999.995
乘法用value結果:5.000000000000000104083408558608425664715468883514404296875000
乘法用string結果:5.000
絕對值用value結果:1000
絕對值用string結果:1000
除法用value結果:199999.99999999999583666366
除法用string結果:200000.00000000000000000000

這裏出現了差異,這也是爲什麼初始化建議使用string的原因

System.out.println()中的數字默認是double類型的,double類型小數計算不精準。

 

因爲不是所有的浮點數都能夠被精確的表示成一個double 類型值,有些浮點數值不能夠被精確的表示成 double 類型值,因此它會被表示成與它最接近的 double 類型的值。必須改用傳入String的構造方法。這一點在BigDecimal類的構造方法註釋中有說明。

 

2.2.1 除法divide()參數使用需要注意

      BigDecimal除法可能出現不能整除的情況,比如 4.5/1.3,這時會報錯java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

      其實divide方法有可以傳三個參數:public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 第一參數表示除數, 第二個參數表示小數點後保留位數,第三個參數表示舍入模式,只有在作除法運算或四捨五入時纔用到舍入模式,

八種舍入模式解釋如下

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

      斷言請求的操作具有精確的結果,因此不需要舍入。如果對獲得精確結果的操作指定此舍入模式,則拋出ArithmeticException。

      2.2.1部分參考 http://www.bdqn.cn/news/201311/11834.shtml

2.2.2 BegDecimal的比較

//使用compareTo方法比較

BigDecimal a = new BigDecimal (101);
BigDecimal b = new BigDecimal (111);
 

//注意:a、b均不能爲null,否則會報空指針
if(a.compareTo(b) == -1){
    System.out.println("a小於b");
}
 
if(a.compareTo(b) == 0){
    System.out.println("a等於b");
}
 
if(a.compareTo(b) == 1){
    System.out.println("a大於b");
}
 
if(a.compareTo(b) > -1){//等價於>=0
    System.out.println("a大於等於b");
}
 
if(a.compareTo(b) < 1){//等價於<=0
    System.out.println("a小於等於b");
}

2.2.3 相互轉化

int 轉換成 BigDecimal 數據類型

  //int  轉換成  bigDecimal類型
    public static void intToBigDecimal(){
        int b = 5;
        BigDecimal a = new BigDecimal(b);
        System.out.println(a +"的數據類型是"+a.getClass().getName());
    }

Lont 轉換成 BigDecimal 數據類型

  //Long 類型轉換成 bigDecimal
    public static void longToBigDecimal(){
        long b = 5;
        BigDecimal a = new BigDecimal(b);
        System.out.println(a +"的數據類型是"+a.getClass().getName());
    }

BigDecimal 轉換成 Long數據類型

  //bigDecimal  轉換成  Long類型
    public static void bigDecimalToLong(){
        BigDecimal b = new BigDecimal(12);
        Long c = b.longValue();
        System.out.println(c+"的數據類型是"+c.getClass().getName());
    }

BigDecimal 轉換成 int數據類型

    //bigDecimal  轉換成  int類型
        public static void bigDecimalToInt(){
            BigDecimal b = new BigDecimal(12); 
            int a = b.intValue();
        }

 

 

 

 

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