背景
從事金融相關項目届吁,對BigDecimal
應該是再熟悉不過了,也有很多人因為不知道绿鸣、不了解或使用不當導致資損事件發(fā)生疚沐。
所以,如果你從事金融相關項目潮模,或者你的項目中涉及到金額的計算亮蛔,那么你一定要花時間看看這篇文章,全面學習一下BigDecimal
概述
一般情況下擎厢,對于不需要準確計算精度的數字究流,可以直接使用Float
和Double
處理,但是 Float
和Double
會導致精度丟失动遭。所以在需要精確計算結果的項目中芬探,則必須使用BigDecimal
類來操作。雖然厘惦,BigDecimal
比 Float
和Double
能夠保證精度問題偷仿,但是使用不當也會踩坑。
四個BigDecimal 中容易踩的坑
1、創(chuàng)建 BigDecimal 的坑
在BigDecimal
中提供了多種創(chuàng)建方式炎疆,可以通過new
直接創(chuàng)建卡骂,也可以通過 BigDecimal#valueOf
創(chuàng)建。這兩種方式使用不當形入,也會導致精度問題全跨。如下:
@Test
public void test(){
BigDecimal b1 = new BigDecimal(0.1);
BigDecimal b2 = BigDecimal.valueOf(0.1);
System.out.println("b1=" + b1);
System.out.println("b2=" + b2);
}
輸出結果:
b1=0.1000000000000000055511151231257827021181583404541015625
b2=0.1
上面示例中 b1
還是出現(xiàn)了精度的問題。造成這種問題的原因是 0.1 這個數字計算機是無法精確表示的亿遂,送給 BigDecimal 的時候就已經丟精度了浓若,而 BigDecimal#valueOf
的實現(xiàn)卻完全不同。如下源碼所示蛇数,BigDecimal#valueOf
中是把浮點數轉換成了字符串來構造的BigDecimal
挪钓,因此避免了問題。
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));
}
結論
:
第一耳舅,在使用BigDecimal構造函數時碌上,盡量傳遞字符串而非浮點類型;
第二浦徊,如果無法滿足第一條馏予,則可采用BigDecimal#valueOf方法來構造初始化值。
2盔性、等值比較的坑
一般在比較兩個值是否相等時霞丧,都是用equals
方法,但是冕香,在BigDecimal
中使用equals
可能會導致結果錯誤蛹尝,BigDecimal
中提供了 compareTo
方法,在很多時候需要使用compareTo
比較兩個值悉尾。如下所示:
@Test
public void testEquals(){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("1.00");
System.out.println(b1.equals(b2));
System.out.println(b1.compareTo(b2));
}
輸出結果:
false
0
出現(xiàn)此種結果的原因是突那,equals
不僅比較了值相等,還比較了精度是否相同构眯。示例中陨收,由于兩個值的精度不同,所有結果也就不相同鸵赖。而 compareTo
是值比較值的大小。返回的值為-1(小于)拄衰,0(等于)它褪,1(大于)。
結論
如果比較兩個BigDecimal
值的大小翘悉,采用其實現(xiàn)的compareTo
方法茫打;
如果嚴格限制精度的比較,那么則可考慮使用equals
方法。
3老赤、無限精度的坑
BigDecimal
并不代表無限精度轮洋,當在兩個數除不盡的時候惋戏,就會出現(xiàn)無限精度的坑党觅,如下所示:
@Test
public void testDivide(){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
b1.divide(b2);
}
輸出結果:
java.lang.ArithmeticException: Non-terminating decimal expansion;
no exact representable decimal result.
在官方文檔中有如下說明:
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)運算過程中册倒,如果商是一個無限小數(0.333…)订雾,而操作的結果預期是一個精確的數字始鱼,那么將會拋出ArithmeticException異常琅坡。
此種情況春叫,只需要在使用 divide
方法時指定結果的精度即可:
@Test
public void testDivide(){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
BigDecimal divide = b1.divide(b2, 2, RoundingMode.HALF_UP);
System.out.println(divide);
}
結論
在使用BigDecimal
進行(所有)運算時责鳍,盡量指定精度和舍入模式碾褂。
4、輸出字符串的坑
在BigDecimal
轉換成字符串時历葛,有可能輸出非你預期的結果正塌。如下所示:
@Test
public void testString(){
BigDecimal a = BigDecimal.valueOf(332334535345456700.12345634534534578901);
System.out.println(a.toString());
}
輸出結果:
3.323345353454567E+17
可以看到結果已經被轉換成了科學計數法,可能這個并不是預期的結果 恤溶,BigDecimal
有三個方法可以轉為相應的字符串類型乓诽,切記不要用錯:
String toString(); // 有必要時使用科學計數法
String toPlainString(); // 不使用科學計數法
String toEngineeringString(); // 工程計算中經常使用的記錄數字的方法,與科學計數法類似宏娄,但要求10的冪必須是3的倍數
小結
在金融項目中问裕,一般都是推薦使用BigDecimal
,來避免精度的丟失孵坚。但是BigDecimal
使用不當也會踩坑粮宛。本章內容主要介紹了在使用BigDecimal
時經常容易踩的坑。雖然某些場景下推薦使用BigDecimal
卖宠,它能夠達到更好的精度巍杈,但性能相較于double
和float
,還是有一定的損失的扛伍,特別在處理龐大筷畦,復雜的運算時尤為明顯。故一般精度的計算沒必要使用BigDecimal
刺洒。而必須使用時鳖宾,一定要規(guī)避上述的坑。