踩坑一:創(chuàng)建時精度丟失
在BigDecimal 中提供了多種創(chuàng)建方式稽荧,可以通過new 直接創(chuàng)建橘茉,也可以通過 valueOf 創(chuàng)建。這兩種方式使用不當(dāng),也會導(dǎo)致精度問題畅卓。如下:
public static void main(String[] args) throws Exception {
BigDecimal b1= new BigDecimal(0.1);
System.out.println(b1);
BigDecimal b2= BigDecimal.valueOf(0.1);
System.out.println(b2);
BigDecimal b3= BigDecimal.valueOf(0.111111111111111111111111111234);
System.out.println(b3);
}
執(zhí)行結(jié)果:
0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111
上面示例中兩個方法都傳入了double 類型的參數(shù) 0.1擅腰,但是 b1 還是出現(xiàn)了精度的問題。
造成這種問題的原因是 0.1 這個數(shù)字計算機(jī)是無法精確表示的髓介,
給 BigDecimal 的時候就已經(jīng)丟精度了惕鼓,而BigDecimal#valueOf 的實(shí)現(xiàn)卻完全不同筋现。
如下源碼所示唐础,BigDecimal# valueOf 中是 先把把浮點(diǎn)數(shù)轉(zhuǎn)換成了字符串 來構(gòu)造的BigDecimal,因此避免了問題矾飞。
public static BigDecimal valueOf(double val) {
return new BigDecimal(Double.toString(val));
}
結(jié)論:
1> 使用 BigDecimal 構(gòu)造函數(shù)時一膨,盡量傳遞 字符串 而非 浮點(diǎn)類型;
2> 如果無法滿足第一條洒沦,則可采用 BigDecimal#valueOf
方法來構(gòu)造初始化值豹绪。但 valueOf 受 double 類型精度影響,當(dāng)傳入?yún)?shù)小數(shù)點(diǎn)后的位數(shù) 超過 double 允許的16位精度申眼,還是可能會出現(xiàn)問題的瞒津。
踩坑二:等值比較的坑
一般在比較兩個值是否相等時,都是用equals 方法括尸,但是巷蚪,在BigDecimal 中使用equals可能會導(dǎo)致結(jié)果錯誤,BigDecimal 中提供了 compareTo 方法濒翻,在很多時候需要使用compareTo 比較兩個值屁柏。如下所示:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("1.00");
System.out.println(b1.equals(b2));
System.out.println(b1.compareTo(b2));
}
執(zhí)行結(jié)果:
false
0
原因是
1> equals不僅比較了值是否相等,還比較了精度是否相同有送。
示例中淌喻,由于兩個值的精度不同,所有結(jié)果也就不相同雀摘。
2> compareTo 是只比較值的大小裸删。返回的值為 -1(小于),0(等于)阵赠,1(大于)涯塔。
結(jié)論:
如果比較兩個 BigDecimal 值大小,采用 compareTo
方法豌注;
如果 嚴(yán)格限制精度的比較伤塌,那么則可考慮使用 equals
方法。
踩坑三:無限精度
BigDecimal 并不代表無限精度轧铁,當(dāng)在兩個數(shù)除不盡的時候每聪,就會出現(xiàn)無限精度的坑,如下所示:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
b1.divide(b2);
}
執(zhí)行結(jié)果:
在官方文檔中對該異常有如下說明:
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.
大致意思就是,如果在除法(divide)運(yùn)算過程中药薯,如果商是一個無限小數(shù)(如 0.333…)绑洛,而操作的結(jié)果預(yù)期是一個精確的數(shù)字,那么將會拋出 ArithmeticException 異常童本。
此種情況真屯,只需要在使用 divide方法時指定結(jié)果的精度即可:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
System.out.println(b1.divide(b2,2, RoundingMode.HALF_UP)); //0.33
}
結(jié)論:
在使用BigDecimal進(jìn)行(所有)運(yùn)算時,盡量指定精度 和 舍入模式穷娱。
踩坑四:BigDecimal 三種字符串輸出
在 BigDecimal 轉(zhuǎn)換成字符串時绑蔫,有可能輸出非你預(yù)期的結(jié)果。
如下所示:
public static void main(String[] args){
BigDecimal bg = new BigDecimal("1E11");
System.out.println(bg.toString()); // 1E+11
System.out.println(bg.toPlainString()); // 100000000000
System.out.println(bg.toEngineeringString()); // 100E+9
}
執(zhí)行結(jié)果:
1E+11
100000000000
100E+9
下面介紹 java.math.BigDecimal 下的三個toString方法的區(qū)別及用法
- toString() :有必要時使用科學(xué)計數(shù)法泵额。
- toPlainString() : 不使用任何指數(shù)配深。
- toEngineeringString():有必要時使用工程計數(shù)法。
工程記數(shù)法 是一種 工程計算 中經(jīng)常使用的記錄數(shù)字的方法嫁盲,與科學(xué)技術(shù)法類似篓叶,但要求 10的冪必須是3的倍數(shù)。
踩坑五:使用BigDecimal進(jìn)行計算時參數(shù)不能為NULL
在使用BigDecimal 進(jìn)行加羞秤、減缸托、乘、除瘾蛋、比較大小時俐镐,
要保證參與計算的兩個值不能為空,否則會有 NPE 瘦黑。
代碼示例:
BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = null;
System.out.println("相加:" + b2.add(b1));
結(jié)果:
踩坑六:除法計算時 除數(shù)不能為0
代碼示例:
BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = new BigDecimal("0");
System.out.println(b1.divide(b2));
執(zhí)行結(jié)果:
踩坑七:執(zhí)行順序不能調(diào)換(乘法交換律失效)
乘法滿足交換律是一個常識京革,但是在計算機(jī)的世界里,會出現(xiàn)不滿足乘法交換律的情況幸斥;
代碼示例:
BigDecimal b1 = BigDecimal.valueOf(1.0);
BigDecimal b2 = BigDecimal.valueOf(3.0);
BigDecimal b3 = BigDecimal.valueOf(3.0);
// b1 / b2 * b3
System.out.println(b1.divide(b2, 2, RoundingMode.HALF_UP).multiply(b3)); // 0.990
// b1 * b3 / b2
System.out.println(b1.multiply(b3).divide(b2, 2, RoundingMode.HALF_UP)); // 1.00
執(zhí)行結(jié)果:
0.990
1.00
執(zhí)行順序交換后匹摇,產(chǎn)生的結(jié)果可能不同,會導(dǎo)致一定的問題甲葬,使用順序建議先乘后除廊勃。