BigDecimal 是 Java 中的一個類,這個相信大家都是知道的。它的作用就是可以表示任意精度的十進制數,BigDecimal 提供了精確的數字運算,適用于需要高精度計算的場景,例如金融、貨幣或者稅收等涉及到金錢的地方。
與 double 和 float 不同的是,BigDecimal 對象在計算的過程中不會丟失精度,那么下面我們就來看下第一個坑,浮點精度的坑。
我們先來看一個例子:
public static void main(String[] args) { BigDecimal num1 = new BigDecimal("0.1"); BigDecimal num2 = new BigDecimal("0.10"); // false System.out.println(num1.equals(num2)); // 0 System.out.println(num1.compareTo(num2)); }
compareTo 方法比較中,a.compareTo(b)
返回:
在上方的代碼中,我們使用 new BigDecimal 的形式 new 了兩個 BigDecimal 對象,分別是 0.1 和0.10。
我們分別使用了 equals 與 compareTo 進行比較,當使用 equals 進行比較時,返回了 false,這是因為 equals 不僅比較了值是否相等,還比較了精度是否相等,源碼中是這樣寫的:
public boolean equals(Object x) { if (!(x instanceof BigDecimal)) return false; BigDecimal xDec = (BigDecimal) x; if (x == this) return true; if (scale != xDec.scale) return false; long s = this.intCompact; long xs = xDec.intCompact; if (s != INFLATED) { if (xs == INFLATED) xs = compactValFor(xDec.intVal); return xs == s; } else if (xs != INFLATED) return xs == compactValFor(this.intVal); return this.inflated().equals(xDec.inflated()); }
所以在使用 equals 進行比較兩個 BigDecimal 的大小時,一定要注意這一點了。
簡單概括一下,如果比較兩個 BigDecimal 對象的大小,那就使用 compareTo 方法;如果嚴格比較精度的大小,那就使用 equals 方法進行比較。
上面我們知道了如何比較兩個 BigDecimal 對象的大小,equals 比較的還有他們的精度,那么精度又是如何設置的呢,這塊有沒有坑呢?
有的同學可能會說了,設置精度還有啥坑啊,設置了精度就好了嗎,哎對,就是這個意思,在做 BigDecimal 對象計算的時候,一定要設置精度。相反,有的同學就不喜歡設置精度,那么這 BUG 不就來了嗎。
來看一個例子:
public static void main(String[] args) { BigDecimal num1 = new BigDecimal("1"); BigDecimal num2 = new BigDecimal("3"); BigDecimal result = num1.divide(num2); // 默認舍入模式為 UNNECESSARY,會拋出 ArithmeticException }
上述的代碼在執行結束之后會報錯 ArithmeticException ,這是因為默認舍入模式為 UNNECESSARY,所以會拋出 ArithmeticException。
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
要解決這個異常也很容易,只需要加上精度即可。
public static void main(String[] args) { BigDecimal num1 = new BigDecimal("1"); BigDecimal num2 = new BigDecimal("3"); BigDecimal result = num1.divide(num2, 2,RoundingMode.HALF_UP); // 輸出:0.33 System.out.println(result); }
那么出現這個異常的原因是什么你考慮過嗎?為什么加了精度就不報錯了呢?
這個異常在源碼中也有說明:
大概意思就是如果在做 divide 運算時,如果商是一個無限小數,而操作的結果是一個精確的數字,那么就會拋出該異常。
不知道大家注意到一點沒有,就是上面做除法運算的時候,也就是 BigDecimal result = num1.divide(num2, 2,RoundingMode.HALF_UP); 這行代碼的位置,使用了一個新的變量 result 來接收結果值,因為 BigDecimal 是不可變的,因此每次進行運算都會創建一個新的 BigDecimal 對象,所以這一點也是需要注意的,創建的多了可能會產生大量的垃圾對象。
講完了精度與運算,那么你初始化的方式對嗎?
先來看代碼:
BigDecimal num = new BigDecimal(0.1); // 使用雙精度浮點數構造System.out.println(num); // 輸出: 0.1000000000000000055511151231257827021181583404541015625BigDecimal num2 = new BigDecimal("0.1"); // 使用字符串構造System.out.println(num2); // 輸出: 0.1
在使用 new BigDecimal 構造器進行初始化的時候,如果有初始值,最好使用字符串的構造方法進行初始化。
在使用 double 的構造器進行新建時,本身傳入的 0.1 就是浮點類型了,為了不丟失精度,在使用 new BigDecimal 新建時就把這個近似值完整的保留下來了。
或者就是 另外一種初始化方式 BigDecimal.valueOf(0.1);,通過看源碼可以發現,在 valueOf 的內部,將 Double 類型直接轉為了字符串了,因此也就不會存在精度丟失的問題了。
對于使用 new BigDecimal(0.1) 構造時,源碼中也已經說明了這個問題。
大體意思就是生成的 BigDecimal 對象不是我們想要的 0.1,推薦使用 String 類型的構造方法。
上面我們已經學會了如何初始化,如何運算,下一步就是如何用了,例如轉字符串,很多同學可能會說,轉字符串 toString() 不就好了,如果你也這樣想,那你單純了弟弟。
還是先看一段代碼:
public static void main(String[] args) { BigDecimal a = BigDecimal.valueOf(89382389312389594.33822312317952678768725); System.out.println(a.toString()); // 輸出:8.93823893123896E+16 String str = a.setScale(2, RoundingMode.HALF_UP).toString(); System.out.println(str); // 輸出: 89382389312389600.00 }
上面代碼中是一個非常大的數,我想把他轉為字符串,可是在使用 toString() 方法時,打印出來的卻是科學計數法。
所以如果想使用 toString() 方法進行轉字符串時,可以使用設置精度的方法,但是結果還是與我們的預期有所差別,我們想要的是一模一樣的打印出來呢?
那么 toPlainString 就上場了,這個方法返回一個字符串的表示形式,包含所有的有效數字。
代碼修改如下:
public static void main(String[] args) { BigDecimal a = BigDecimal.valueOf(89382389312389594.99933822312317952678768725); System.out.println(a.toPlainString()); }
修改之后就可以了嗎,不可以,忘了上面說的嗎,使用 String 的構造函數吧兄弟,double 類型的構造函數會丟失精度的。
最終代碼如下:
public static void main(String[] args) { BigDecimal a = new BigDecimal("89382389312389594.99933822312317952678768725"); System.out.println(a.toPlainString()); }
除了上述兩種轉字符串的方法外,還有一種,就是 toEngineeringString,這個方法也是返回一個字符串,包含有效數字,但是它會使用工程計數法,科學計數法的一種變體,它使用數字的倍數來表示值,使得指數是 3 的倍數。例如,1000會顯示為"1E3",而不是"1E+3"。
所以總結就是:
本文從精度的比較、除法運算中是否設置精度、對象初始化到轉字符串,四個角度來把 BigDecimal 的坑盡可能清晰的描述出來,以及基于這些坑得到的優秀實踐。
有些場景下推薦使用 BigDecimal ,但是能不用還是不用,比 double 、float 多出來的性能損失得是你能接受的。如果非得用,那上面這幾個坑一定要規避。
本文鏈接:http://www.tebozhan.com/showinfo-26-91825-0.html不掌握 BigDecimal 的四大坑你敢用嗎?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: C語言字符串為什么以/0 作為結束標志?
下一篇: 從0到1,手把手教你部署自己的線上項目