大家好,我是蘇三,又跟大家見面了。
前言
在日常開發中,很多小夥伴喜歡用 BigDecimal 來處理精確計算,比如錢、分數、比例啥的。
理論上,它比 double 或 float 更精確,但如果你用得不對,精度丟失的問題會讓你哭暈在廁所。
今天我們就來聊聊 ,錯誤使用BigDecimal的6種場景,為什麼會發生問題,以及怎麼避免問題,希望對你會有所幫助。
1 直接用浮點數初始化
不少小夥伴習慣這樣寫:
BigDecimal num = new BigDecimal(0.1);
System.out.println(num);
列印結果:0.1000000000000000055511151231257827021181583404541015625
並非列印的:0.1
問題出在哪?
這不是 BigDecimal 的問題,而是浮點數本身的“鍋”。
在Java中,double的精度有限的,0.1 轉換成二進位制是個無限迴圈小數,直接傳進去會帶上誤差。
正確姿勢是傳字串:
BigDecimal num = new BigDecimal("0.1");
System.out.println(num);
列印結果:0.1,是正確的。
注意:永遠不要用 BigDecimal(double) 建構函式,用字串或整數更靠譜。也可以使用BigDecimal.valueOf()函式。
2 加減乘除時不設精度
有些小夥伴做加減乘除的時候,直接寫:
BigDecimal a = new BigDecimal("1.03");
BigDecimal b = new BigDecimal("0.42");
//減法
BigDecimal result = a.subtract(b);
System.out.println(result);
列印結果::0.61,沒問題。
但問題在 除法 時:
BigDecimal c = new BigDecimal("10");
BigDecimal d = new BigDecimal("3");
BigDecimal result = c.divide(d);
執行直接炸了:java.lang.ArithmeticException: Non-terminating decimal expansion
報錯的根本原因:10/3 是無限小數,BigDecimal 預設不保留小數點後面,精度溢位。
那麼,我們要如何最佳化呢?
答:加一個 MathContext 或指定精度。
例如:
BigDecimal result = c.divide(d, 2, RoundingMode.HALF_UP);
System.out.println(result);
列印結果:3.33,可以正常執行。
因此,我們需要注意,在BigDecimal 做除法時 ,必須指定精度。
3 用 equals 判斷相等
BigDecimal 的 equals 會比較 值和精度,這坑了不少人:
BigDecimal x = new BigDecimal("1.0");
BigDecimal y = new BigDecimal("1.00");
System.out.println(x.equals(y));
列印結果:false。
儘管 1.0 和 1.00 的數值相等,但精度不一樣,equals 判定為不同。
最佳化方法,用 compareTo 比較數值:
例如:
System.out.println(x.compareTo(y) == 0);
列印結果:true
需要特別注意的地方是:我們在判斷兩個BigDecimal物件是否相等時,應該用 compareTo方法,別用 equals方法。
4 使用 scale 時忽視實際含義
有些小夥伴搞不清 scale(小數位數)和 precision(總位數)的區別,直接寫:
BigDecimal num = new BigDecimal("123.4500");
System.out.println(num.scale());
列印結果:4
但如果你寫成下面這樣的:
BigDecimal stripped = num.stripTrailingZeros();
System.out.println(stripped.scale());
列印結果卻是:2
scale 會發生變化,搞不好會影響後續計算。
那麼,我們要如何最佳化方法呢?
答:明確 scale 的含義。
如果要固定小數位,使用 setScale:
BigDecimal fixed = num.setScale(2, RoundingMode.HALF_UP);
System.out.println(fixed);
列印結果:123.45。
我們不要混淆 scale 和 precision,必要時顯式設定小數位數。
5 忽略不可變性
BigDecimal 是不可變的,但有些小夥伴會這樣寫:
BigDecimal sum = new BigDecimal("0");
for (int i = 0; i < 5; i++) {
sum.add(new BigDecimal("1"));
}
列印結果:0
問題原因是 add 方法不會改變原物件,而是返回一個新的 BigDecimal 例項。
那麼,我們要如何最佳化呢?
答:用變數接住返回值。
BigDecimal sum = new BigDecimal("0");
for (int i = 0; i < 5; i++) {
sum = sum.add(new BigDecimal("1"));
}
System.out.println(sum);
列印結果是:5
BigDecimal 操作後需要接住新例項。
6 忽視效能問題
BigDecimal 是很精確,但也很慢。
如果大量計算時用 BigDecimal,會拖累效能,比如計算利息:
BigDecimal principal = new BigDecimal("10000");
BigDecimal rate = new BigDecimal("0.05");
BigDecimal interest = principal.multiply(rate);
一個迴圈裡搞上百萬次,效能直接拉垮。
那麼,這種情況我們又該如何最佳化呢?
答:能用整數就用整數(比如分代替元)。
批次計算時,用 double 計算,結果最後轉換成 BigDecimal。
double principal = 10000;
double rate = 0.05;
BigDecimal interest = BigDecimal.valueOf(principal * rate);
System.out.println(interest);
列印結果:500.00
參與大批次計算時,兩個BigDecimal物件直接計算會比較慢,儘量少用,能最佳化的地方別放過。
寫在最後
BigDecimal 是個非常強大的數字類工具,但也是個“細節狂魔”。
只有用對了,你才能真正享受它帶來的好處,否則就是自找麻煩。
希望這篇文章能幫到你,不要再踩坑。
如果有其他用法上的困惑,歡迎留言討論,我們一起成長!
最後說一句(求關注,別白嫖我)
如果這篇文章對您有所幫助,或者有所啟發的話,幫忙關注一下我的同名公眾號:蘇三說技術,您的支援是我堅持寫作最大的動力。
求一鍵三連:點贊、轉發、在看。
關注公眾號:【蘇三說技術】,在公眾號中回覆:進大廠,可以免費獲取我最近整理的10萬字的面試寶典,好多小夥伴靠這個寶典拿到了多家大廠的offer。