java中BigDecimal的介紹及使用教程BigDecimal格式化及BigDecimal常見問題
一、BigDecimal概述
BigDecimal是Java在java.math包中提供的線程安全
的API類,用來對超過16位有效位的數(shù)進行精確的運算。雙精度浮點型變量double可以處理16位有效數(shù),但在實際應用中,可能需要對更大或者更小的數(shù)進行運算和處理。一般情況下,對于那些不需要準確計算精度的數(shù)字,我們可以直接使用Float和Double處理,但是Double.valueOf(String) 和Float.valueOf(String)會丟失精度。所以開發(fā)中,如果我們需要精確計算的結果,則必須使用BigDecimal類來操作。
? BigDecimal所創(chuàng)建的是對象,故我們不能使用傳統(tǒng)的+、-、*、/等算術運算符直接對其對象進行數(shù)學運算,而必須調用其相對應的方法。方法中的參數(shù)也必須是BigDecimal的對象。構造器是類的特殊方法,專門用來創(chuàng)建對象,特別是帶有參數(shù)的對象。
二、BigDecimal常用構造方法
方法 | 含義 |
---|---|
BigDecimal(int val) | 創(chuàng)建一個具有參數(shù)所指定整數(shù)值的對象。 |
BigDecimal(double val) | 創(chuàng)建一個具有參數(shù)所指定雙精度值的對象。 不推薦使用,因為存在精度丟失問題 |
BigDecimal(long val) | 創(chuàng)建一個具有參數(shù)所指定長整數(shù)值的對象。 |
BigDecimal(String val) | 創(chuàng)建一個具有參數(shù)所指定以字符串表示的數(shù)值的對象。 推薦使用 |
三、BigDecimal常用方法
注意:BigDecimal進行運算時必須要保證對象本身不能是null,否則就會拋空指針異常。
方法 | 含義 |
---|---|
add(BigDecimal) | BigDecimal對象中的值相加,返回BigDecimal對象 |
subtract(BigDecimal) | BigDecimal對象中的值相減,返回BigDecimal對象 |
multiply(BigDecimal) | BigDecimal對象中的值相乘,返回BigDecimal對象 |
divide(BigDecimal) | BigDecimal對象中的值相除,返回BigDecimal對象。 該方法可能會遇到無限精度問題,會拋出異常,使用時需注意。詳細見下方的無限精度的坑 |
abs() | 將BigDecimal對象中的值轉換成絕對值 |
doubleValue() | 將BigDecimal對象中的值轉換成雙精度數(shù) |
floatValue() | 將BigDecimal對象中的值轉換成單精度數(shù) |
longValue() | 將BigDecimal對象中的值轉換成長整數(shù) |
intValue() | 將BigDecimal對象中的值轉換成整數(shù) |
compareTo(BigDecimal val) | 比較大小,返回int類型。0(相等) 1(大于) -1(小于) |
toString() | 有必要時使用科學計數(shù)法。 |
toPlainString() | 不使用任何指數(shù)。 推薦使用 |
toEngineeringString() | 有必要時使用工程計數(shù)法。 工程記數(shù)法是一種工程計算中經常使用的記錄數(shù)字的方法,與科學技術法類似,但要求10的冪必須是3的倍數(shù) |
max(BigDecimal val) | 兩值比較,返回最大值 |
negate() | 求相反數(shù),正變負,負變正 |
pow(int n) | 求乘方,如BigDecimal.valueOf(2).pow(3)的值為8 |
代碼示例
import java.math.BigDecimal; public class Test { public static void main(String[] args){ BigDecimal b1 = new BigDecimal("1"); BigDecimal b2 = new BigDecimal("2"); BigDecimal b3 = new BigDecimal("4"); System.out.println("相加:"+b1.add(b2)); System.out.println("相減:"+b1.subtract(b2)); System.out.println("相乘:"+b2.multiply(b3)); System.out.println("相除:"+b2.divide(b3)); } }
四、BigDecimal進階
BigDecimal的八種舍入模式
BigDecimal.setScale()方法用于格式化小數(shù)點
setScale(1)表示保留一位小數(shù),默認用四舍五入方式
setScale(1,BigDecimal.ROUND_DOWN)直接刪除多余的小數(shù)位,如2.35會變成2.3
setScale(1,BigDecimal.ROUND_UP)進位處理,2.35變成2.4
setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35變成2.4
setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35變成2.3,如果是5則向下舍
setScaler(1,BigDecimal.ROUND_CEILING)接近正無窮大的舍入
setScaler(1,BigDecimal.ROUND_FLOOR)接近負無窮大的舍入,數(shù)字>0和ROUND_UP作用一樣,數(shù)字<0和ROUND_DOWN作用一樣
setScaler(1,BigDecimal.ROUND_HALF_EVEN)向最接近的數(shù)字舍入,如果與兩個相鄰數(shù)字的距離相等,則向相鄰的偶數(shù)舍入。
1、ROUND_UP,向遠離0的方向舍入,在丟棄非零部分之前始終增加數(shù)字(始終對非零舍棄部分前面的數(shù)字加1)。
注意,此舍入模式始終不會減少計算值的大小。
eg: 保留1位小數(shù) 1.60->1.6 1.61->1.7 1.66->1.7 , -1.62->-1.7
2、ROUND_DOWN,向接近0的方向舍入,在丟棄某部分之前,始終不增加數(shù)據(jù)(即,截斷),該方式是只減不加。
eg: 保留1位小數(shù) 1.60->1.6 1.61->1.6 1.66->1.6 , -1.62->-1.6
3、ROUND_CEILING,向正無窮方向舍入,如果數(shù)值為正,舍入方式與ROUND_UP一致,如果為負,舍入方式與ROUND_DOWN一致,該模式始終不會減少計算數(shù)值。
eg: 保留1位小數(shù) 1.60->1.6 1.61->1.7 1.66->1.7 , -1.62->-1.6
4、ROUND_FLOOR,向負無窮方向舍入,如果數(shù)值為正,舍入行為與 ROUND_DOWN 相同;如果為負,則舍入行為與 ROUND_UP 相同。該模式始終不會增加計算數(shù)值。
eg: 保留1位小數(shù) 1.60->1.6 1.61->1.6 1.66->1.6 , -1.62->-1.7
5、ROUND_HALF_UP,向“最接近的”數(shù)字舍入,也就是四舍五入。
eg: 保留1位小數(shù) 1.61->1.6 1.65->1.7 1.66->1.7 , -1.62->-1.6
6、ROUND_HALF_DOWN,向“最接近的”數(shù)字舍入,如果與兩個相鄰數(shù)字的距離相等,則為上舍入的舍入模式,也就是五舍六入。
eg: 保留1位小數(shù) 1.61->1.6 1.65->1.6 1.66->1.7 , -1.62->-1.6
7、ROUND_HALF_EVEN,向“最接近的”數(shù)字舍入,如果與兩個相鄰數(shù)字的距離相等,則向相鄰的偶數(shù)舍入。如果舍棄部分左邊的數(shù)字為奇數(shù),則舍入行為與 ROUND_HALF_UP 相同;如果為偶數(shù),則舍入行為與 ROUND_HALF_DOWN 相同。
注意,在重復進行一系列計算時,此舍入模式可以將累加錯誤減到最小。
此舍入模式也稱為“銀行家舍入法”,主要在美國使用。四舍六入,五分兩種情況。如果前一位為奇數(shù),則入位,否則舍去。以下例子為保留小數(shù)點1位,那么這種舍入方式下的結果。
eg. 1.15->1.2, 1.25->1.2
8、ROUND_UNNECESSARY,計算結果是精確的,不需要舍入模式。如果對獲得精確結果的操作指定此舍入模式,則拋出ArithmeticException。
代碼示例
BigDecimal b = new BigDecimal("1.6666"); System.out.println("result b:" + b.setScale(2, BigDecimal.ROUND_HALF_UP)); // 1.67 System.out.println("result b:" + b.setScale(2)); // 精度錯誤
執(zhí)行結果:
result b:1.67
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
原因分析:
setScale方法默認使用的roundingMode是ROUND_UNNECESSARY,不需要使用舍入模式,設置精度2位,但是小數(shù)點后有4位肯定會拋異常。
BigDecimal格式化、小數(shù)點轉換
BigDecimal可以與DecimalFormat結合使用,從而對金額格式化,如小數(shù)點后面統(tǒng)一保留兩位,不夠兩位的補零,多余兩位的舍入。
import java.math.BigDecimal; import java.text.DecimalFormat; public class Test { public static void main(String[] s){ System.out.println(formatToNumber(new BigDecimal("12333.435"))); System.out.println(formatToNumber(new BigDecimal(0))); System.out.println(formatToNumber(new BigDecimal("0.00"))); System.out.println(formatToNumber(new BigDecimal("0.001"))); System.out.println(formatToNumber(new BigDecimal("0.006"))); System.out.println(formatToNumber(new BigDecimal("0.206"))); System.out.println(formatToNumber(new BigDecimal("1.22"))); } /** * @desc * @param obj 傳入的小數(shù) * @return */ public static String formatToNumber(BigDecimal obj) { // DecimalFormat默認使用的是進位方式是RoundingMode.HALF_EVEN,此舍入模式也稱為“銀行家算法”,主要在美國使用。 //銀行家算法:四舍六入五考慮,五后非零就進一,五后為零看奇偶,五前為偶應舍去,五前為奇要進一 DecimalFormat df = new DecimalFormat("###,##0.00"); return df.format(obj); } }
執(zhí)行結果:
12,333.44
0.00
0.00
0.00
0.01
0.21
1.22
需注意:
- DecimalFormat的默認進位方式不是四舍五入,所以當小數(shù)點后面需要舍去的時候,肯能跟預想的不一樣,具體可參考《關于DecimalFormat的取舍問題,DecimalFormat四舍五入的坑》
- new DecimalFormat(“###,##0.00”)小數(shù)點前面需要有個0,這樣0-1之間的數(shù)字才會正常格式化;若##0.00的小數(shù)點前面沒有0,則0-1之間的數(shù)字會被丟失掉小數(shù)點前的0,代碼如下:
import java.math.BigDecimal; import java.text.DecimalFormat; public class Test { public static void main(String[] s){ System.out.println(formatToNumber(new BigDecimal("12333.435"))); System.out.println(formatToNumber(new BigDecimal(0))); System.out.println(formatToNumber(new BigDecimal("0.00"))); System.out.println(formatToNumber(new BigDecimal("0.001"))); System.out.println(formatToNumber(new BigDecimal("0.006"))); System.out.println(formatToNumber(new BigDecimal("0.206"))); System.out.println(formatToNumber(new BigDecimal("1.22"))); } /** * @desc * @param obj 傳入的小數(shù) * @return */ public static String formatToNumber(BigDecimal obj) { // DecimalFormat默認使用的是進位方式是RoundingMode.HALF_EVEN,此舍入模式也稱為“銀行家算法”,主要在美國使用。 //銀行家算法:四舍六入五考慮,五后非零就進一,五后為零看奇偶,五前為偶應舍去,五前為奇要進一 DecimalFormat df = new DecimalFormat("###,##.00"); return df.format(obj); } }
執(zhí)行結果:
1,23,33.44
.00
.00
.00
.01
.21
1.22
貨幣格式化與百分比格式化
經常能看到金額用¥120.00表示,利率用0.8%表示,這里擴展一下BigDecimal的貨幣格式化與百分比格式化
NumberFormat類的format()方法可以使用BigDecimal對象作為其參數(shù),可以利用BigDecimal對超出16位有效數(shù)字的貨幣值,百分值,以及一般數(shù)值進行格式化控制。
NumberFormat對象:
getCompactNumberInstance();返回FORMAT帶有"SHORT"格式樣式的默認語言環(huán)境 的緊湊數(shù)字格式 。
getCurrencyInstance?(Locale inLocale);返回指定語言環(huán)境的貨幣格式。若是不指定參數(shù),則以默認語言為參數(shù)。
getInstance?(Locale inLocale);返回指定語言環(huán)境的通用數(shù)字格式。若是不指定參數(shù),則以默認語言為參數(shù)。
getPercentInstance?(Locale inLocale);返回指定語言環(huán)境的百分比格式。若是不指定參數(shù),則以默認語言為參數(shù)。
代碼示例:
import java.math.BigDecimal; import java.text.NumberFormat; public class Test { public static void main(String[] args){ NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立貨幣格式化引用 NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用 percent.setMinimumFractionDigits(2);//設置數(shù)的小數(shù)部分所允許的最小位數(shù)(如果不足后面補0) percent.setMaximumFractionDigits(3);//設置數(shù)的小數(shù)部分所允許的最大位數(shù)(如果超過會四舍五入) BigDecimal amount = new BigDecimal("250600.42"); //金額 BigDecimal interestRate = new BigDecimal("0.0004"); //利率 BigDecimal interest = amount .multiply(interestRate); //相乘 System.out.println("金額: " + currency.format(loanAmount)); System.out.println("利率: " + percent.format(interestRate)); System.out.println("利息: " + currency.format(interest)); } }
執(zhí)行結果:
金額: ¥250,600.42
利率: 0.04%
利息: ¥100.24
NumberFormat提供了多種貨幣格式的引用,如¥(人民幣),$(美元、英元)等等,具體的后續(xù)將再寫一篇博文介紹。
五、BigDecimal常見問題
踩坑一:創(chuàng)建 BigDecimal精度丟失的坑
在BigDecimal 中提供了多種創(chuàng)建方式,可以通過new 直接創(chuàng)建,也可以通過 BigDecimal#valueOf 創(chuàng)建。這兩種方式使用不當,也會導致精度問題。如下:
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í)行結果:
0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111
上面示例中兩個方法都傳入了double類型的參數(shù)0.1但是 b1 還是出現(xiàn)了精度的問題。造成這種問題的原因是 0.1 這個數(shù)字計算機是無法精確表示的,送給 BigDecimal 的時候就已經丟精度了,而 BigDecimal#valueOf 的實現(xiàn)卻完全不同。如下源碼所示,BigDecimal#valueOf 中是把浮點數(shù)轉換成了字符串來構造的BigDecimal,因此避免了問題。
public static BigDecimal valueOf(double val) { return new BigDecimal(Double.toString(val)); }
結論:
- 第一,在使用BigDecimal構造函數(shù)時,盡量傳遞字符串而非浮點類型;
- 第二,如果無法滿足第一條,則可采用BigDecimal#valueOf方法來構造初始化值。
但是valueOf受double類型精度影響,當傳入參數(shù)小數(shù)點后的位數(shù)超過double允許的16位精度還是可能會出現(xiàn)問題的
踩坑二:等值比較的坑
一般在比較兩個值是否相等時,都是用equals 方法,但是,在BigDecimal 中使用equals可能會導致結果錯誤,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í)行結果:
false
0
出現(xiàn)此種結果的原因是,equals不僅比較了值是否相等,還比較了精度是否相同。示例中,由于兩個值的精度不同,所有結果也就不相同。而 compareTo 是只比較值的大小。返回的值為-1(小于),0(等于),1(大于)。
結論
- 如果比較兩個BigDecimal值的大小,采用其實現(xiàn)的compareTo方法;
- 如果嚴格限制精度的比較,那么則可考慮使用equals方法。
踩坑三:無限精度的坑
BigDecimal 并不代表無限精度,當在兩個數(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í)行結果:
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.demo.controller.Test.main(Test.java:29)
在官方文檔中對該異常有如下說明:
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)運算過程中,如果商是一個無限小數(shù)(如 0.333…),而操作的結果預期是一個精確的數(shù)字,那么將會拋出ArithmeticException異常。
此種情況,只需要在使用 divide方法時指定結果的精度即可:
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 }
結論:
- 在使用BigDecimal進行(所有)運算時,盡量指定精度和舍入模式。
踩坑四:BigDecimal三種字符串輸出的坑
在BigDecimal 轉換成字符串時,有可能輸出非你預期的結果。如下所示:
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í)行結果:
1E+11
100000000000
100E+9
可以看到三種方式輸出的結果可能都不相同,可能這個并不是預期的結果 ,BigDecimal 有三個方法可以轉為相應的字符串類型,切記不要用錯:
以下內容介紹java.math.BigDecimal下的三個toString方法的區(qū)別及用法
toPlainString() : 不使用任何指數(shù)。
toString() :有必要時使用科學計數(shù)法。
toEngineeringString():有必要時使用工程計數(shù)法。 工程記數(shù)法是一種工程計算中經常使用的記錄數(shù)字的方法,與科學技術法類似,但要求10的冪必須是3的倍數(shù)
具體三種方法的區(qū)別可參見另一篇博文《BigDecimal的toString()、toPlainString()和toEngineeringString()區(qū)別及用法》
踩坑五:使用BigDecimal進行計算時參數(shù)不能為NULL
在使用BigDecimal類型進行計算時,進行加、減、乘、除、比較大小時,一定要保證參與計算的兩個值不能為空,否則會拋出java.lang.NullPointerException異常。
代碼示例:
BigDecimal b1 = new BigDecimal("1"); BigDecimal b2 = null; System.out.println("相加:"+b2.add(b1));
結果:
Exception in thread "main" java.lang.NullPointerException
at com.demo.controller.Test.main(Test.java:14)
踩坑六:使用BigDecimal進行除法計算時被除數(shù)不能為0
代碼示例:
BigDecimal b1 = new BigDecimal("1"); BigDecimal b2 = new BigDecimal("0"); System.out.println(b1.divide(b2));
執(zhí)行結果:
Exception in thread "main" java.lang.ArithmeticException: Division by zero
踩坑七:執(zhí)行順序不能調換(乘法交換律失效)
乘法滿足交換律是一個常識,但是在計算機的世界里,會出現(xiàn)不滿足乘法交換律的情況;
代碼示例:
BigDecimal b1 = BigDecimal.valueOf(1.0); BigDecimal b2 = BigDecimal.valueOf(3.0); BigDecimal b3 = BigDecimal.valueOf(3.0); System.out.println(b1.divide(b2, 2, RoundingMode.HALF_UP).multiply(b3)); // 0.990 System.out.println(b1.multiply(b3).divide(b2, 2, RoundingMode.HALF_UP)); // 1.00
執(zhí)行結果:
0.990
1.00
執(zhí)行順序交換后,產生的結果可能不同,會導致一定的問題,使用順序建議先乘后除。
到此這篇關于java中BigDecimal的介紹及使用,BigDecimal格式化,BigDecimal常見問題的文章就介紹到這了,更多相關java BigDecimal格式化內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用Spring @DependsOn控制bean加載順序的實例
這篇文章主要介紹了使用Spring @DependsOn控制bean加載順序的實例講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java Web開發(fā)之基于Session的購物商店實現(xiàn)方法
這篇文章主要介紹了Java Web開發(fā)之基于Session的購物商店實現(xiàn)方法,涉及Java針對session的操作及數(shù)據(jù)庫操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10Java語言----三種循環(huán)語句的區(qū)別介紹
下面小編就為大家?guī)硪黄狫ava語言----三種循環(huán)語句的區(qū)別介紹。小編舉得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-07-07druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換方式
這篇文章主要介紹了druid多數(shù)據(jù)源配置+Datasurce動態(tài)切換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09