又踩坑了!BigDecimal使用的5個坑!

码农谈IT發表於2024-04-12

來源:撿田螺的小男孩

前言

大家好,我是田螺。

在日常開發中,BigDecimal類被廣泛用於精確的數值、金額的計算。但是在使用BigDecimal的過程中,存在以下這幾個坑,大家要注意一下哈~~

1.浮點數初始化的坑

反例:

BigDecimal problematic = new BigDecimal(0.1);
System.out.println("Problematic: " + problematic.toString());
//輸出 Problematic: 0.1000000000000000055511151231257827021181583404541015625

可以發現,直接使用new BigDecimal(double)構造方法可能會得到一個看起來相當奇怪的結果。這是因為double本身的精度問題會被帶入BigDecimal中。為了避免這個問題,推薦使用String引數的構造方法或者使用BigDecimal.valueOf方法,例如new BigDecimal("0.1"),這樣可以確保BigDecimal的精度。

由於計算機的資源是有限的,所以是沒辦法用二進位制精確的表示 0.1,只能用「近似值」來表示,就是在有限的精度情況下,最大化接近 0.1 的二進位制數,於是就會造成精度缺失的情況。

正例:

BigDecimal problematic1 =  BigDecimal.valueOf(0.1);
System.out.println("Problematic: " + problematic1.toString());

BigDecimal problematic2 = new BigDecimal("0.1");
System.out.println("Problematic: " + problematic2.toString());
//輸出
Problematic: 0.1
Problematic: 0.1

2. 比較數值時使用compareTo()方法而非equals()

BigDecimal bd1 = new BigDecimal("0.10");
BigDecimal bd2 = new BigDecimal("0.1");
System.out.println(bd1.equals(bd2)); // 輸出: false
System.out.println(bd1.compareTo(bd2) == 0); // 輸出: true

BigDecimal的equals方法不僅比較數值,還會比較物件的scale(小數點後的位數),如果只想比較數值,而不考慮scale,應該使用compareTo方法。

又踩坑了!BigDecimal使用的5個坑!

3.做除法時,未指定精度可能異常

BigDecimal dividend = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("3");
BigDecimal result = dividend.divide(divisor); // 未指定精度和舍入模式
System.out.println(result);
//輸出
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1693)
at com.example.demo.controller.Test.main(Test.java:26)

除法操作將10除以3,結果是無限迴圈小數3.3333...,但由於未指定精度和舍入模式,會丟擲ArithmeticException異常。

官方有給出解釋:

"If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations."

要使用BigDecimal時,要記得指定精度,避免因為精度問題帶來的損失。

4. BigDecimal轉String,科學計數法展示問題

System.out.println( new BigDecimal("0.0000000000001").toString());
BigDecimal bigDecimal = new BigDecimal("1E+12");
System.out.println(bigDecimal.toString());
//輸出 1E-13
//輸出 1E+12

這是因為 toString() 某些場景下使用科學計數法展示。如果不想用任何計數法,可以使用 toPlainString()

System.out.println( new BigDecimal("0.0000000000001").toPlainString());
BigDecimal bigDecimal = new BigDecimal("1E+12");
System.out.println(bigDecimal.toPlainString());
//輸出
0.0000000000001
1000000000000

5. 注意使用setScale方法設定精度

BigDecimal number = new BigDecimal("123.4567");
BigDecimal roundedNumber = number.setScale(2, RoundingMode.HALF_UP);
//輸出
123.46

因為BigDecimal的精度是無限的,因此一般在計算的時候,要注意設定精度幾位。

並且,RoundingMode.HALF_UP 是一種舍入模式,用於四捨五入,即當數字的一部分被捨去時,如果剩餘部分大於或等於0.5,則向上舍入。除了HALF_UP之外,還有幾個常用的舍入模式:

  • UP:遠離零方向舍入的舍入模式。總是在非零捨棄部分之前增加數字。
  • DOWN:接近零方向舍入的舍入模式。總是在非零捨棄部分之前減少數字。
  • CEILING:接近正無窮大的方向舍入的舍入模式。如果BigDecimal是正的,則舍入行為與UP相同;如果是負的,則舍入行為與DOWN相同。
  • FLOOR:接近負無窮大的方向舍入的舍入模式。如果BigDecimal是正的,則舍入行為與DOWN相同;如果是負的,則舍入行為與UP相同。
  • HALF_DOWN:向“最近鄰居”舍入,除非兩邊距離相等,此時向下舍入。
  • HALF_EVEN:向“最近鄰居”舍入,除非兩邊距離相等,此時向偶數舍入。這種模式也稱為“銀行家舍入法”,因為它減少了累計錯誤。

還有一個點,就是:使用setScale方法實際上會產生一個全新的BigDecimal例項,而不會更改原有物件。所以,當你用setScale調整了數字精度後,別忘了把新生成的物件賦值回原來的變數。

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70024924/viewspace-3012315/,如需轉載,請註明出處,否則將追究法律責任。

相關文章