1. 程式人生 > >java中數字基本運算、金額運算精度問題小結

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.45s21->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、合理利用一些成熟可靠的第三方工具類,可以給數字相關運算帶來很大的便利。