java中數字基本運算、金額運算精度問題小結
一、前言
在我們日常工作中,經常會有涉及到數字的運算,其中金額的運算尤其重要且敏感,因為金額的運算若不注意處理的話,很容易因為精度的丟失,從而導致最終資料的異常,造成嚴重的系統錯誤。本文將對java中金額的運算處理進行簡單小結。
二、NumberFormat類、DecimalFormat類、BigDecimal類簡介
1、NumberFormat類
NumberFormat類是所有數值格式的抽象基類,它繼承了Format抽象類。NumberFormat類提供了格式化和分析數值的介面,還提供了一些方法來確定哪些語言環境具有數值格式,以及它們的名稱是什麼。其常用的方法說明如下:
//返回當前預設語言環境的預設數值格式
public final static NumberFormat getInstance();
//返回當前預設語言環境的通用格式
public final static NumberFormat getCurrencyInstance();
//返回當前預設語言環境的通用數值格式
public final static NumberFormat getNumberInstance();
//返回當前預設語言環境的百分比格式
public final static NumberFormat getPercentInstance();
另外,這幾個方法都有相應的通過Locale類指定當前環境的方法。
例1:
//輸出格式化後的數字
public class NumberTest {
public static void main(String[] args) {
double a = 12345.123456;
double b = 0.123456;
double c = 12345.67896789;
double d = 0.125555;
String s1 = NumberFormat.getInstance().format(a);
String s2 = NumberFormat.getCurrencyInstance ().format(a);
String s3 = NumberFormat.getNumberInstance().format(a);
String s4 = NumberFormat.getPercentInstance().format(b);
String s5 = NumberFormat.getInstance().format(c);
String s6 = NumberFormat.getPercentInstance().format(d);
System.out.println("s1->" + s1);
System.out.println("s2->" + s2);
System.out.println("s3->" + s3);
System.out.println("s4->" + s4);
System.out.println("s5->" + s5);
System.out.println("s6->" + s6);
}
}
輸出
s1->12,345.123
s2->¥12,345.12
s3->12,345.123
s4->12%
s5->12,345.679
s6->13%
注意:格式化後,保留位數小數位後是四捨五入的。
當然,如果說你想對某一個數字精確保留指定位數的話,可以通過相關引數來設定。
例2:
public class NumberTest {
public static void main(String[] args) {
double a = 12345.6789;
double b = 1.2;
NumberFormat format = NumberFormat.getInstance();
//設定數值的整數部分允許的最大位數
format.setMaximumIntegerDigits(3);
//設定數值的整數部分允許的最小位數
format.setMinimumIntegerDigits(3);
// 設定數值的小數部分允許的最大位數
format.setMaximumFractionDigits(3);
//設定數值的小數部分允許的最小位數
format.setMinimumFractionDigits(3);
System.out.println("a->" + format.format(a));
System.out.println("b->" + format.format(b));
}
}
輸出
a->345.679
b->001.200
2、DecimalFormat類
DecimalFormat類是NumberFormat的一個具體子類,用於格式化十進位制數字,通常用於涉及高精度的運算。DecimalFormat類主要靠 # 和 0 兩種佔位符號來指定數字長度;0 表示如果位數不足則以 0 填充,# 表示只要有可能就把數字拉上這個位置。
例3:
public class NumberTest {
public static void main(String[] args) {
double a = 123.456789;
double b = 1.2;
double c = 0.12345;
long d = 123456789;
//最少取2位整數,整數不足部分以0填補
String s1 = new DecimalFormat("00").format(a);
//最少取1位整數、取3位小數
String s2 = new DecimalFormat("0.000").format(a);
//最少取2位整數、取3位小數,位數不足以0填補
String s3 = new DecimalFormat("00.000").format(b);
//取所有整數部分
String s4 = new DecimalFormat("#").format(a);
//以百分比方式計數,並最多取兩位小數
String s5 = new DecimalFormat("#.##%").format(c);
//以百分比方式計數,且整數部分、小數部分都保留2位,位數不足以0填補
String s6 = new DecimalFormat("00.00%").format(b);
//"\u2030"表示乘以1000並顯示為千分數,要放在最後
String s7 = new DecimalFormat("00.00\u2030").format(c);
//顯示為科學計數法,並取5位小數
String s21 = new DecimalFormat("#.#####E0").format(d);
//顯示為2位整數的科學計數法,並取4位小數
String s22 = new DecimalFormat("00.####E0").format(d);
//每3位以逗號進行分隔
String s23 = new DecimalFormat(",###").format(d);
//每3位以逗號進行分隔,且最少三位
String s24 = new DecimalFormat(",000").format(b);
//將格式嵌入文字
String s25 = new DecimalFormat("嵌入的數字是,###這個數").format(d);
//用#和0的唯一區別是0在數位不足時會自動補足
String s31 = new DecimalFormat("00.00").format(a);
String s32 = new DecimalFormat("##.##").format(a);
String s33 = new DecimalFormat("00.00").format(b);
String s34 = new DecimalFormat("##.##").format(b);
//可以用applyPattern()方法修改Format的模式
DecimalFormat sf = new DecimalFormat("00");
String s41 = sf.format(a);
sf.applyPattern("0.000");
String s42 = sf.format(a);
System.out.println("s1->" + s1);
System.out.println("s2->" + s2);
System.out.println("s3->" + s3);
System.out.println("s4->" + s4);
System.out.println("s5->" + s5);
System.out.println("s6->" + s6);
System.out.println("s7->" + s7);
System.out.println("s21->" + s21);
System.out.println("s22->" + s22);
System.out.println("s23->" + s23);
System.out.println("s24->" + s24);
System.out.println("s25->" + s25);
System.out.println("s31->" + s31);
System.out.println("s32->" + s32);
System.out.println("s33->" + s33);
System.out.println("s34->" + s34);
System.out.println("s41->" + s41);
System.out.println("s42->" + s42);
}
}
輸出
s1->123
s2->123.457
s3->01.200
s4->123
s5->12.34%
s6->120.00%
s7->123.45‰
s21->1.23457E8
s22->12.3457E7
s23->123,456,789
s24->001
s25->嵌入的數字是123,456,789這個數
s31->123.46
s32->123.46
s33->01.20
s34->1.2
s41->123
s42->123.457
備註:用#和0的唯一區別是0在數位不足時會自動補足。更多Format模式詳情請參考JDK文件。
3、BigDecimal類
float、double型別的主要設計目標是為了科學計算和工程計算,並沒有提供完全精確的結果;java.math包下的BigDecimal類則可以滿足高精度運算結果,通常用於商業上的精確運算。
BigDecimal類運算結果精確,也可用於大數的運算,但BigDecimal類的運算是通過建構函式建立運算物件,然後對物件進行運算,因此,其不能像 int 、float 、double 、long 等資料型別的數字,直接使用+ 、- 、* 、/ 等算術運算子對其物件進行數學運算 ,而必須呼叫其相對應的方法進行運算。
其常用構造方法如下:
序號 | 方法 | 描述 |
---|---|---|
1 | public BigDecimal(double val); | 將double表示形式轉換為BigDecimal |
2 | public BigDecimal(int val); | 將int表示形式轉換為BigDecimal |
3 | public BigDecimal(long val); | 將long表示形式轉換為BigDecimal |
4 | public BigDecimal(String val); | 將String表示形式轉換為BigDecimal |
其中,因為String構造方法的結果是完全可預知的, 所以通常建議優先使用。
其常用運算方法如下:
序號 | 方法 | 描述 |
---|---|---|
1 | public BigDecimal add(BigDecimal augend); | 加法 |
2 | public BigDecimal subtract(BigDecimal subtrahend); | 減法 |
3 | public BigDecimal multiply(BigDecimal multiplicand); | 乘法 |
4 | public BigDecimal divide(BigDecimal divisor); | 除法; 如果準確的商值沒有無窮的十進位制擴充套件,拋ArithmeticException異常 |
5 | public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode); | 除法;設定精確位數、保留位數的策略。RoundingMode為舍入模式,更多舍入模式請查閱API文件 |
6 | public int compareTo(BigDecimal val); | 比較;當此 BigDecimal 在數字上小於、等於或大於 val 時,返回 -1、0 或 1 |
7 | public int scale(); | 返回此 BigDecimal 的標度(即小數點後位數) |
8 | public BigDecimal setScale(int newScale, RoundingMode roundingMode); | 返回 BigDecimal,其標度為指定值,其非標度值通過此 BigDecimal 的非標度值乘以或除以十的適當次冪來確定,以維護其總值 |
例4:
public class NumberTest {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("12.345");
BigDecimal b = new BigDecimal("6.78");
String s1 = a.add(b).toString();
String s2 = a.subtract(b).toString();
String s3 = a.multiply(b).toString();
String s4 = a.divide(b,5,RoundingMode.HALF_UP).toString(); //四捨五入
String s5 = new BigDecimal("10").divide(new BigDecimal("4")).toString(); //商要是有限位數,否側會拋異常
System.out.println("s1:a+b=" + s1);
System.out.println("s2:a-b=" + s2);
System.out.println("s3:a*b=" + s3);
System.out.println("s4:a/b=" + s4);
System.out.println("s5->" + s5);
}
}
輸出
s1:a+b=19.125
s2:a-b=5.565
s3:a*b=83.69910
s4:a/b=1.82080
s5->2.5
三、處理數字精度問題常用方法
通過以上NumberFormat類、DecimalFormat類、BigDecimal類這3個類的簡單介紹,我們可以知道,在不用的應用場景下,通過靈活運用這3個類及相關類,我們就可以實現高精度的運算。具體用法可參照以上介紹,下面對數字運算做一些其它方面的補充。
1、在業務系統涉及到金額時,不少人的做法是,金額的單位為元(業務顯示一般為元),然後運算、DB儲存時均使用double型別、保留2位小數。其實這樣很容易造成精度的丟失,更合適的做法是:我們用分來表示金額的單位,在運算的時候直接用int、long資料型別,最後顯示的時候再轉化成元,這樣的話,很大程度上就避免精度丟失的問題了。(金額運算大多數為加、減,乘、除較少)
2、可以合理地運用一些第三方工具包,如:apache.commons.lang3包中的math包,其Fraction 類可用於分數的計算、NumberUtils類可用於數字大小比較、RandomUtils類可用於隨機數操作等。
四、總結
1、儲存計算金額時,最好直接存整數(表示單位為分、釐、毫),然後直接對整數進行加減運算,最後在最終展示的時候,再換算成所需的單位。
2、需要保證精度的運算最好使用BigDecimal類,因為其精度準確,且與其它基本資料型別裝換方便。
3、合理利用一些成熟可靠的第三方工具類,可以給數字相關運算帶來很大的便利。