BigDecimal 的介紹及使用

一、BigDecimal 的介紹

? ? BigDecimal是Java在java.math包中提供的API類,用來對超過16位有效位的數進行精確的運算。雖然double類型的變量也可以處理16位有效數,但是在實際應用中,也會出現對更大或者更小的數進行運算和處理的。float和double只能用來做科學計算或者是工程計算。
????BigDecimal創建的是對象,不能使用傳統的+、-、*、/等算數運算符來對其進行數學運算,而是應該采用該類下面的對應的方法,方法中的參數也必須是BigDecimal對象。
? ? 一般情況下,對于那些不需要準確精度的數字,我們可以直接使用Float和Double處理,但是Double.valueOf(Stirng)和Float.valueOf(String)會丟失精度,所以開發中,如果我們需要精確計算的結果,則必須使用BigDecimal類來操作。

二、BigDecimal的構造方法

? ? 1、常用構造函數
? ? ? ? BigDecimal(int):創建一個具有參數所指定整數值的對象;
? ? ? ? BigDecimal(double):創建一個具有參數所指定雙精度值的對象;
? ? ? ? BigDecimal(long):創建一個具有參數所指定的長整數值的對象;
? ? ? ? BigDecimal(String):創建一個具有參數所指定以字符串表示的數值的對象。
? ? 2、使用注意:
? ? ? ? 當使用double類型創建BigDecimal對象的時候,可能會出現精度誤差,不建議使用。如果想要創建Double類型的,建議使用new BigDecimal(Double.toString(bigDecimal))的形式。
? ? ? ? 代碼詳解:

public static void main(String[] args) {
? ? double dou = 0.1;
????BigDecimal big1 = new BigDecimal(dou);
????System.out.println("big1 = " + big1);
????BigDecimal big2 = new BigDecimal(Double.toString(dou));
????System.out.println("big2 = " + big2);
}
結果:
big1?= 0.1000000000000000055511151231257827021181583404541015625
big2?= 0.1

圖片粘不上來,就將結果直接粘到上面了,有興趣的同學自己去IDE里運行一下,看結果是否一致。
? ? 原因:
? ? ? ? 參數類型為double的構造方法的結果具有一定的不可預知性。就像上面的程序表示的那樣,雖然new BigDecimal(0.1)所創建的Bigdecimal是0.1,但是實際上得到的值確是不同的,因為0.1無法準確地表示為double,所以傳入構造方法的值也就出現了誤差。
? ? ? ? 而String 類型的構造方法是完全可以預知到的,參數給的是“0.1”,得到的結果就是0.1.所以使用該類型的構造方法更能符合自己預期中的值。
? ? ? ? 當double必須用作BigDecimal的源時,可以使用BigDecimal.valueOf(double)的方法來進行,因為該方法的底層,也是將double值轉換為String類型,在進行運算的。

public static BigDecimal valueOf(double val) {
?????// Reminder: a zero double returns '0.0', so we cannot fastpath
?????// to use the constant ZERO. This might be important enough to
?????// justify a factory approach, a cache, or a few private
?????// constants, later.
?????return new BigDecimal(Double.toString(val));
}
// ps:不知道今天怎么了,圖片都貼不上來,只能將源碼粘上來了。
// 這里可以看到,valueOf的底層還是?BigDecimal(String)實現的。

? ? 3、為什么使用double類型會出現精度誤差
? ? ? ? 其實我們平時使用的語言大多數都有精度不準確的情況,不是Java獨有的。
????????出現這種情況的原因就要從我們計算機的數據表現形式說起了,計算機底層所有數據的表現形式都為二進制形式,一般情況下,二進制轉為十進制所使用的方法是按權相加法,十進制轉二進制是除2取余的逆序排列法。

二進制到十進制:
10010:?? 0 * 2^0 + 1 * 2^1 + 0 * 2^2 + 0 * 2^3 + 1 * 2^4 = 18? ?

十進制到二進制:
18 / 2 =9。 余0
9 / 2 = 4。? 余1
4 / 2 = 2。? 余0
2 / 2 = 1。? 余0
1 / 2 = 0。? 余1
得出的結果就是10010
進制數之間的轉換相信大家已經很熟了,如果忘記的,看這個算法大概都能回想起來了吧,這里就不做詳細解釋了

? ? 上面是整數轉換的方法,在小數部分,使用乘2取整數位,順序排列。二進制小數到十進制小數還是按權相加法。

二進制到十進制:
10.01 = 1 * 2^-2 + 0 * 2^-1 + 0 * 2^0 + 1 * 2^1 = 2.25

十進制到二進制(小數部分):
0.25
0.25 * 2 = 0.5 .... 0
0.5 * 2 = 1 .... 1
結果等于0.01

那是進制之間的轉換問題,那為什么浮點運算會有不準確的情況呢?下面進入正題,以2.1舉例

// 整數部分?
?2 / 2 = 1 .... 0
?1 / 2 = 0 .... 1
// 小數部門
0.1 * 2 = 0.2 .... 0
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0?
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0?
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
0.2 * 2 = 0.4 .... 0
0.4 * 2 = 0.8 .... 0
0.8 * 2 = 1.6 .... 1
0.6 * 2 = 1.2 .... 1
............

? ? 這就是無限循環,結果為 10.0001100110011........ , 我們的計算機在存儲小數時肯定是有長度限制的,所以會進行截取部分小數進行存儲,從而導致計算機存儲的數值只能是個大概的值,而不是精確的值。從這里看出來我們的計算機根本就無法使用二進制來精確的表示 2.1 這個十進制數字的值,連表示都無法精確表示出來,計算肯定是會出現問題的。

三、BigDecimal常用方法詳解

? 1、常用方法:
????add(BigDecimal):BigDecimal對象中的值相加,返回BigDecimal對象
????subtract(BigDecimal):BigDecimal對象中的值相減,返回BigDecimal對象
????multiply(BigDecimal):BigDecimal對象中的值相乘,返回BigDecimal對象
????divide(BigDecimal):BigDecimal對象中的值相除,返回BigDecimal對象
????abs():將BigDecimal對象中的值轉換成絕對值
????doubleValue():將BigDecimal對象中的值轉換成雙精度數
? ? floatValue():將BigDecimal對象中的值轉換成單精度數
????longValue():將BigDecimal對象中的值轉換成長整數
????intValue():將BigDecimal對象中的值轉換成整數
????toString():將BigDecimal對象中的值轉換成字符串
? 2、大小比較
? ??java中對BigDecimal比較大小一般用的是BigDemical的compareTo方法.
? ? ? ? 舉例:new BigDemica("0.1").compareTo(new BigDemical(0.2))
? ? 關于這些方法的使用,大家可以去IDE中執行一下,非常好上手,這里不做詳細展示了。

四、BigDecimal的八種舍入模式

????BigDecimal.setScale()方法用于格式化小數點
????setScale(1)表示保留一位小數,默認用四舍五入方式
????setScale(1,BigDecimal.ROUND_DOWN)直接刪除多余的小數位,如2.35會變成2.3
????setScale(1,BigDecimal.ROUND_UP)進位處理,2.35變成2.4
????setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35變成2.4
????setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35變成2.3,如果是5則向下舍
????setScaler(1,BigDecimal.ROUND_CEILING)接近正無窮大的舍入
????setScaler(1,BigDecimal.ROUND_FLOOR)接近負無窮大的舍入,數字>0和ROUND_UP作用一樣,數字<0和ROUND_DOWN作用一樣
????setScaler(1,BigDecimal.ROUND_HALF_EVEN)向最接近的數字舍入,如果與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。

? ? 1、ROUND_UP,向遠離0的方向舍入,始終對非零舍棄部位前面的數字+1,該方式就是只增不減。
? ? 2、ROUND_DOWN,向0方向舍入,在丟棄某部分之前,始終不增加數據(即,截斷),該方式是只減不加。
? ? 3、ROUND_CEILING,向正無窮方向舍入,如果數值為正,舍入方式與ROUND_UP一致,如果為負,舍入方式與ROUND_DOWN一致,該模式始終不會減少計算數值。
? ? 4、ROUND_FLOOR,向負無窮方向舍入,如果數值為正,舍入行為與 ROUND_DOWN 相同;如果為負,則舍入行為與 ROUND_UP 相同。該模式始終不會增加計算數值。
? ? 5、ROUND_HALF_UP,向“最接近的”數字攝入,也就是四舍五入。
? ? 6、ROUND_HALF_DOWN,向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則為上舍入的舍入模式,也就是五舍六入。
? ? 7、ROUND_HALF_EVEN,向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。如果舍棄部分左邊的數字為奇數,則舍入行為與 ROUND_HALF_UP 相同;如果為偶數,則舍入行為與 ROUND_HALF_DOWN 相同。(向(距離)最近的一邊舍入,除非兩邊(的距離)是相等,如果是這樣,如果保留位數是奇數,使用四舍五入,如果是偶數,使用五舍六入),此模式也被稱為“銀行家舍入法”,主要在美國使用。
? ? ? ? eg.? ?1.15->1.2,? ? ? 1.25->1.2
? ? 8、ROUND_UNNECESSARY,計算結果是精確的,不需要舍入模式。如果對獲得精確結果的操作指定此舍入模式,則拋出ArithmeticException。

五、總結

? ??BigDecimal的性能比double和float都差,在處理龐大、復雜的運算時尤為明顯,所以一般精度的計算沒必要使用BigDecimal,在需要精確的小數計算時再使用。
? ? 創建BigDecimal對象時,盡量使用參數類型為String的構造函數。
? ??BigDecimal都是不可變的,在進行每一次四則運算時,都會產生一個新對象,所以在做加減乘除運算時要保存一下操作后的值哦。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容