天天看點

一文了解BigDecimal全部

以前就有想法花一點時間學習一下Java中用于表示高精度運算的類BigDecimal

了解之後,總結一下以示學習

1. BigDecimal是什麼

先看一組例子:

public static void main(String[] s) {
        double a = 0.1, b = 0.2;
        System.out.println(a + b);
    }
           
輸出結果:
0.30000000000000004
           

事與願違,結果并不是我們想象的0.3;

原因很簡單,計算機在做運算的時候,會先将十進制變成二進制,再對二進制進行運算;在十進制轉換為二進制的時候,有時候不能使用有限的一個二進制來表達,尤其是遇到有小數的情況,即出現了精度丢失問題,進而導緻運算的時候出現了事與願違的一幕。如0.1轉換為二進制是

0.000110011001100110011001100…………

既然有問題,那就必有解決辦法;在Java中是使用BigDecimal 來解決;是java.math包中提供的API類,用來對超過16位有效位的數進行精确的運算。除了加減乘除運算,還提供了格式化,舍入等方法。

2. BigDecimal常用構造方法
  • BigDecimal(int)

    建立一個具有參數所指定整數值的對象

  • BigDecimal(double)

    建立一個具有參數所指定雙精度值的對象

  • BigDecimal(long)

    建立一個具有參數所指定長整數值的對象

  • BigDecimal(String)

    建立一個具有參數所指定以字元串表示的數值的對象

問題分析:

public static void main(String[] s) {
        BigDecimal bigDecimal = new BigDecimal("0.1");
        BigDecimal bigDecimal2 = new BigDecimal("0.2");
        BigDecimal bigDecimal3 = new BigDecimal(0.1);  // 傳double構造方法
        BigDecimal bigDecimal4 = new BigDecimal(0.2);  // 傳double構造方法
        System.out.println(bigDecimal.add(bigDecimal2));
        System.out.println(bigDecimal3.add(bigDecimal4));
    }
           
輸出結果:
0.3
0.3000000000000000166533453693773481063544750213623046875
           
原因:
  1. 使用BigDecimal類構造方法傳入double類型時,計算的結果也是不精确的!因為不是所有的浮點數都能夠被精确的表示成一個double

    類型值,有些浮點數值不能夠被精确的表示成 double 類型值,是以它會被表示成與它最接近的 double 類型的值。

  2. String 構造方法是完全可預知的:寫入 newBigDecimal(“0.1”) 将建立一個 BigDecimal,它正好等于預期的 0.1。是以,通常建議優先使用String構造方法。也是官方文檔推薦的。
3. BigDecimal常用方法
BigDecimal bigDecimal = new BigDecimal("0.1");
        BigDecimal bigDecimal2 = new BigDecimal("0.2");
        BigDecimal bigDecimal3 = new BigDecimal("-0.2");
        // 加
        System.out.println(bigDecimal.add(bigDecimal2));
        // 乘
        System.out.println(bigDecimal.multiply(bigDecimal2));
        // 減
        System.out.println(bigDecimal.subtract(bigDecimal2));
        // 除
        System.out.println(bigDecimal.divide(bigDecimal2));
        // 絕對值
        System.out.println(bigDecimal3.abs());
        // 比較大小,-1小于,0等于,1大于
        System.out.println(bigDecimal.compareTo(bigDecimal2));
           
運作結果:
0.3
0.02
-0.1
0.5
0.2
-1
           
4. BigDecimal根據舍入模式保留小數位

運算的時候保留小數位,并指定某種具體的保留模式

/*
         * 保留小數位(2位),以及舍入模式( RoundingMode.HALF_UP:四舍五入)
         */
        BigDecimal bigDecimal = new BigDecimal("0.333");
        BigDecimal bigDecimal2 = new BigDecimal("0.111");
        // 加
        System.out.println(bigDecimal.add(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
        // 乘
        System.out.println(bigDecimal.multiply(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
        // 減
        System.out.println(bigDecimal.subtract(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
        // 除(三分之一四舍五入模式保留二位小數)
        System.out.println(bigDecimal2.divide(bigDecimal, 2, RoundingMode.HALF_UP));
           
運作結果:
0.44
0.04
0.22
0.33
           
注意:
  1. 值得一提的是,運用除法的時候,如果出現無限循環小數時,就會抛異常:

    java.lang.ArithmeticException:Non-terminating decimal expansion; no exact representable decimal result.

    解決異常的方法就是設定小數點位數

  2. 加減乘和除設定保留小數方式不一樣
  3. 其他的舍入模式可以參見jdk文檔(RoundingMode類),這裡不做介紹,傳送門:http://www.matools.com/api/java8
5. BigDecimal格式化

由于NumberFormat類的format()方法可以使用BigDecimal對象作為其參數,可以利用BigDecimal對超出16位有效數字的貨币值,百分值,以及一般數值進行格式化控制

// 建立貨币格式化引用
		NumberFormat currency = NumberFormat.getCurrencyInstance(); 
		// 建立百分比格式化引用
        NumberFormat percent = NumberFormat.getPercentInstance(); 

        BigDecimal a= new BigDecimal("15.483"); 
        BigDecimal b = new BigDecimal("0.1235"); 
        BigDecimal c= a.multiply(b);

        System.out.println(currency.format(a));
        System.out.println(percent.format(b));
        System.out.println(currency.format(c));
        System.out.println(percent.format(c));
           
運作結果:
¥15.48
12%
¥1.91
191%
           
注意:
  1. 選擇系統預設的貨币格式,且預設保留兩位小數(錢就這樣哈哈),采用四舍五入模式;

    如需選擇其他的貨币格式,可選擇性的傳入Locale裡面的常量,如

    NumberFormat currency1 = NumberFormat.getCurrencyInstance(Locale.US) // $美元

  2. 選擇系統預設的百分比格式,且預設不保留小數位,采用四舍五入模式;

如需指定保留小數位,可使用

setMaximumFractionDigits

getMinimumFractionDigits

設定最大最小的小數位

// 建立貨币格式化引用
        NumberFormat currency = NumberFormat.getCurrencyInstance();
        // 建立百分比格式化引用
        NumberFormat percent = NumberFormat.getPercentInstance();
        // 小數點最多三位,不夠也不用補零
        percent.setMaximumFractionDigits(3);

        BigDecimal a = new BigDecimal("15.483");
        BigDecimal b = new BigDecimal("0.1235");
        BigDecimal c = a.multiply(b);

        System.out.println(currency.format(a));
        System.out.println(percent.format(b));
        System.out.println(currency.format(c));
        System.out.println(percent.format(c));
           
運作結果:
¥15.48
12.35%
¥1.91
191.215%
           

換成:

// 小數點最少三位,不夠補零
        percent.setMinimumFractionDigits(3);
           
運作結果:
¥15.48
12.350%
¥1.91
191.215%
           
提示:
  1. 也可以使用setMaximumIntegerDigits和setMinimumIntegerDigits設定最多和最少整數位,用法和小數位設定一緻

除了NumberFormat,也可以使用它的子類DecimalFormat,對Decimal數值進行格式化(保留小數位)

/*
         * 0:少了用0補,多了切掉,四舍五入
         * #:少了也不補(就空着),多了切掉,四舍五入 
         * 注意:使用#時數值0不能表示一個位置
         */
         DecimalFormat df = new DecimalFormat("0.00");
         System.out.println(df.format(new BigDecimal("3.435")));
         System.out.println(df.format(new BigDecimal(0)));
         System.out.println(df.format(new BigDecimal("0.00")));
         System.out.println(df.format(new BigDecimal("0.001")));
         System.out.println(df.format(new BigDecimal("0.006")));
         System.out.println(df.format(new BigDecimal("10.206")));
         
		 System.out.println("===========");
		 
		 DecimalFormat df1 = new DecimalFormat("0.##");
         System.out.println(df1.format(new BigDecimal("3.435")));
         System.out.println(df1.format(new BigDecimal(0)));
         System.out.println(df1.format(new BigDecimal("0.00")));
         System.out.println(df1.format(new BigDecimal("0.001")));
         System.out.println(df1.format(new BigDecimal("0.006")));
         System.out.println(df1.format(new BigDecimal("10.206")));
           
運作結果:
3.44
0.00
0.00
0.00
0.01
10.21
=========
3.44
0
0
0
0.01
10.21
           
注意:
  1. 0和#都是占位作用,是最多位數的作用,差別在于不夠時#不補,而0補零
  2. #後面不能有0,因為如果位數不夠,#表示不補,0表示補0,這是沖突的,會報異常

    java.lang.IllegalArgumentException: Unexpected ‘0’ in pattern “0.#0”

6. BigDecimal總結
  • 盡量使用參數類型為String的構造函數
  • 系統涉及高精度運算則一定要使用BigDecimal,如果對精度沒有要求,則推薦使用double或float;因為BigDecimal性能低于double和float
  • BigDecimal 在進行每一次四則運算時,都會産生一個新的對象 ,是以在做加減乘除運算時要記得要儲存操作後的值
  • 使用除法的時候,最好設定好要保留的位數,不能很容易因為無限循環出現異常,導緻程式終止
7. BigDecimal工具類推薦

來源于網絡,我也在使用的一個BigDecimal工具類

import java.math.BigDecimal;

/**
 * 用于高精确處理常用的數學運算
 */
public class ArithmeticUtils {
    //預設除法保留小數位數
    private static final int DEF_DIV_SCALE = 10;

    /**
     * 提供精确的加法運算
     * @param v1 被加數
     * @param v2 加數
     * @return 兩個參數的和
     */

    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

    /**
     * 提供精确的加法運算
     * @param v1 被加數
     * @param v2 加數
     * @return 兩個參數的和
     */
    public static BigDecimal add(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2);
    }

    /**
     * 提供精确的加法運算
     * @param v1    被加數
     * @param v2    加數
     * @param scale 保留scale 位小數
     * @return 兩個參數的和
     */
    public static String add(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的減法運算
     * @param v1 被減數
     * @param v2 減數
     * @return 兩個參數的差
     */
    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 提供精确的減法運算。
     * @param v1 被減數
     * @param v2 減數
     * @return 兩個參數的差
     */
    public static BigDecimal sub(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.subtract(b2);
    }

    /**
     * 提供精确的減法運算
     * @param v1    被減數
     * @param v2    減數
     * @param scale 保留scale 位小數
     * @return 兩個參數的差
     */
    public static String sub(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的乘法運算
     * @param v1 被乘數
     * @param v2 乘數
     * @return 兩個參數的積
     */
    public static double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供精确的乘法運算
     * @param v1 被乘數
     * @param v2 乘數
     * @return 兩個參數的積
     */
    public static BigDecimal mul(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.multiply(b2);
    }

    /**
     * 提供精确的乘法運算
     * @param v1    被乘數
     * @param v2    乘數
     * @param scale 保留scale 位小數
     * @return 兩個參數的積
     */
    public static double mul(double v1, double v2, int scale) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return round(b1.multiply(b2).doubleValue(), scale);
    }

    /**
     * 提供精确的乘法運算
     * @param v1    被乘數
     * @param v2    乘數
     * @param scale 保留scale 位小數
     * @return 兩個參數的積
     */
    public static String mul(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供(相對)精确的除法運算,當發生除不盡的情況時,精确到
     * 小數點以後10位,以後的數字四舍五入
     * @param v1 被除數
     * @param v2 除數
     * @return 兩個參數的商
     */

    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供(相對)精确的除法運算。當發生除不盡的情況時,由scale參數指
     * 定精度,以後的數字四舍五入
     * @param v1    被除數
     * @param v2    除數
     * @param scale 表示表示需要精确到小數點以後幾位。
     * @return 兩個參數的商
     */
    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供(相對)精确的除法運算。當發生除不盡的情況時,由scale參數指
     * 定精度,以後的數字四舍五入
     * @param v1    被除數
     * @param v2    除數
     * @param scale 表示需要精确到小數點以後幾位
     * @return 兩個參數的商
     */
    public static String div(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v1);
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的小數位四舍五入處理
     * @param v     需要四舍五入的數字
     * @param scale 小數點後保留幾位
     * @return 四舍五入後的結果
     */
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小數位四舍五入處理
     * @param v     需要四舍五入的數字
     * @param scale 小數點後保留幾位
     * @return 四舍五入後的結果
     */
    public static String round(String v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(v);
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 取餘數
     * @param v1    被除數
     * @param v2    除數
     * @param scale 小數點後保留幾位
     * @return 餘數
     */
    public static String remainder(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 取餘數  BigDecimal
     * @param v1    被除數
     * @param v2    除數
     * @param scale 小數點後保留幾位
     * @return 餘數
     */
    public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 比較大小
     * @param v1 被比較數
     * @param v2 比較數
     * @return 如果v1 大于v2 則 傳回true 否則false
     */
    public static boolean compare(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        int bj = b1.compareTo(b2);
        boolean res;
        if (bj > 0)
            res = true;
        else
            res = false;
        return res;
    }
}