一文了解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. 選擇系統默認的百分比格式,且默認不保留小數位,採用四捨五入模式;

如需指定保留小數位,可使用setMaximumFractionDigitsgetMinimumFractionDigits設置最大最小的小數位

		// 建立貨幣格式化引用
        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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章