Java精確計算

簡介

JAVA的double型數據以及float類型的數據均不能進行精確計算,許多編程語言也是同樣,這與計算機的底層原理有關。java

所以計算得出的結果每每超出預期。編程

尤爲是在金融行業,計算價格或者銀行業務的錢的計算。精確計算變得尤其重要。編程語言

雖然咱們能夠經過四捨五入的方式來處理結果,可是這樣作就意味着存在着偏差。ide

 

場景分析

好比說下面這些計算,我知道結果應該是精確的數字,計算機並無計算出咱們想要的結果。工具

/**
 * @author wzm
 * @version 1.0.0
 * @date 2020/1/25 14:24
 **/
public class MathTest {
 
   public static void main(String[] args) {
       System.out.println(0.05 + 0.01);
       System.out.println(1.0 - 0.43);
       System.out.println(2.03 * 10);
       System.out.println(3.3 / 10);
   }
}

 

計算結果:url

0.060000000000000005
0.5700000000000001
20.299999999999997
0.32999999999999996

 

BigDecimal

Java中提供精確計算的類。java.math.BigDecimalspa

 

四則運算

import java.math.BigDecimal;
 
/**
 * @author wzm
 * @version 1.0.0
 * @date 2020/1/25 14:24
 **/
public class BigDecTest {
 
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("10");
        BigDecimal b = new BigDecimal("5");
        BigDecimal c;
        //加法
        c = a.add(b);
        System.out.println("加法運算:" + c);
        //減法
        c = a.subtract(b);
        System.out.println("加法運算:" + c);
        //除法
        c = a.multiply(b);
        System.out.println("除法運算:" + c);
        //乘法
        c = a.divide(b, BigDecimal.ROUND_CEILING);
        System.out.println("乘法運算:" + c);
    }
}

 

比較大小

BigDecimal v1 = new BigDecimal("-1");
BigDecimal v2 = new BigDecimal("3");
int r = v1.compareTo(v2);
System.out.println(r);
 
* if r==0 --> v1等於v2
* if r==1 --> v1大於v2
* if r==-1 --> v1小於v2

 

舍入模式

BigDecimal定義瞭如下舍入模式,只有在作除法運算或四捨五入時纔會用到舍入模式。code

 

ROUND_CEILINGblog

ROUND_CEILING模式是向正無窮大方向舍入。結果往較大的數值靠。ip

import java.math.BigDecimal;
 
/**
 * @author wzm
 * @version 1.0.0
 * @date 2020/1/25 14:24
 **/
public class BigDecTest {
 
   public static void main(String[] args) {
       int a = 2;
       int b = BigDecimal.ROUND_CEILING;
       System.out.println(new BigDecimal("1.01").setScale(a, b));
       System.out.println(new BigDecimal("1.0100").setScale(a, b));
       System.out.println(new BigDecimal("1.011").setScale(a, b));
       System.out.println(new BigDecimal("1.01001").setScale(a, b));
       System.out.println(new BigDecimal("1.014").setScale(a, b));
       System.out.println(new BigDecimal("-1.01").setScale(a, b));
       System.out.println(new BigDecimal("-1.0100").setScale(a, b));
       System.out.println(new BigDecimal("-1.011").setScale(a, b));
       System.out.println(new BigDecimal("-1.01001").setScale(a, b));
       System.out.println(new BigDecimal("-1.014").setScale(a, b));
   }
}

 

結果:

1.01
1.01
1.02
1.02
1.02
-1.01
-1.01
-1.01
-1.01
-1.01

 

ROUND_FLOOR

ROUND_FLOOR模式是向負無窮大方向舍入。結果往較小的數值靠。

import java.math.BigDecimal;
 
/**
 * @author wzm
 * @version 1.0.0
 * @date 2020/1/25 14:24
 **/
public class BigDecTest {
 
   public static void main(String[] args) {
       int a = 2;
       int b = BigDecimal.ROUND_FLOOR;
       System.out.println(new BigDecimal("1.01").setScale(a, b));
       System.out.println(new BigDecimal("1.0100").setScale(a, b));
       System.out.println(new BigDecimal("1.011").setScale(a, b));
       System.out.println(new BigDecimal("1.01001").setScale(a, b));
       System.out.println(new BigDecimal("1.014").setScale(a, b));
       System.out.println(new BigDecimal("-1.01").setScale(a, b));
       System.out.println(new BigDecimal("-1.0100").setScale(a, b));
       System.out.println(new BigDecimal("-1.011").setScale(a, b));
       System.out.println(new BigDecimal("-1.01001").setScale(a, b));
       System.out.println(new BigDecimal("-1.014").setScale(a, b));
   }
}

 

結果:

1.01
1.01
1.01
1.01
1.01
-1.01
-1.01
-1.02
-1.02
-1.02

 

ROUND_DOWN

ROUND_DOWN模式是向靠近零的方向舍入。

 

ROUND_UP

ROUND_UP模式是向遠離零的方向舍入。

 

ROUND_ UNNECESSARY

ROUND_ UNNECESSARY模式是不使用舍入模式。若是能夠確保計算結果是精確的,則能夠指定此模式,不然若是指定了使用此模式卻遇到了不精確的計算結果,則拋出ArithmeticException。

 

ROUND_HALF_DOWN

ROUND_HALF_DOWN模式是向距離最近的一邊舍入,若是兩邊距離相等,就向靠近零的方向舍入。

 

ROUND_HALF_UP

ROUND_HALF_UP模式是向距離最近的一邊舍入,若是兩邊距離相等,就向遠離零的方向舍入。這個模式在實際的場景中比較經常使用。

 

ROUND_HALF_EVEN

ROUND_HALF_UP模式是向距離最近的一邊舍入,若是兩邊距離相等且小數點後保留的位數是奇數,就使用ROUND_HALF_UP模式;若是兩邊距離相等且小數點後保留的位數是偶數,就使用ROUND_HALF_DOWN模式。

 

工具類

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * java精確計算工具
 *
 * @author wzm
 * @version 1.0.0
 * @date 2020/1/25 14:15
 **/
public class BigDecUtils {

    /**
     * 提供精確加法計算的add方法(整數運算)
     */
    public static String add(String val1, String val2) {
        return add(val1, val2, 0, 0);
    }

    /**
     * 提供精確加法計算的add方法(默認四捨五入)
     *
     * @param val1 被加數
     * @param val2 加數
     * @param scale  精確範圍(小數點後幾位)
     */
    public static String add(String val1, String val2, int scale) {
        return add(val1, val2, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 提供精確加法計算的add方法
     *
     * @param val1    被加數
     * @param val2    加數
     * @param scale     精確範圍(小數點後幾位)
     * @param roundMode 精確模式
     */
    public static String add(String val1, String val2, int scale, int roundMode) {
        BigDecimal b1 = new BigDecimal(val1);
        BigDecimal b2 = new BigDecimal(val2);
        BigDecimal result = b1.add(b2);
        // mode爲0,則不須要精確
        if (roundMode != 0) {
            result = result.setScale(scale, roundMode);
        }
        return result.toString();
    }

    /**
     * 提供精確減法運算的subtract方法
     *
     * @param val1 被減數
     * @param val2 減數
     * @return 兩個參數的差
     */
    public static String sub(String val1, String val2) {
        return sub(val1, val2, 0, 0);
    }

    /**
     * 提供精確減法運算的subtract方法(默認四捨五入)
     *
     * @param val1 被減數
     * @param val2 減數
     * @param scale  精確範圍(小數點後幾位)
     */
    public static String sub(String val1, String val2, int scale) {
        return sub(val1, val2, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 提供精確減法運算的subtract方法
     *
     * @param val1    被減數
     * @param val2    減數
     * @param scale     精確範圍(小數點後幾位)
     * @param roundMode 精確模式
     */
    public static String sub(String val1, String val2, int scale, int roundMode) {
        BigDecimal b1 = new BigDecimal(val1);
        BigDecimal b2 = new BigDecimal(val2);
        BigDecimal result = b1.subtract(b2);
        // mode爲0,則不須要精確
        if (roundMode != 0) {
            result = result.setScale(scale, roundMode);
        }
        return result.toString();
    }

    /**
     * 提供精確的除法運算方法divide
     *
     * @param val1 被除數
     * @param val2 除數
     */
    public static String div(String val1, String val2) throws IllegalAccessException {
        return div(val1, val2, 0, null);
    }

    /**
     * 提供精確的除法運算方法divide(默認四捨五入)
     *
     * @param val1 被除數
     * @param val2 除數
     * @param scale  精確範圍(小數點後幾位)
     */
    public static String div(String val1, String val2, int scale) throws IllegalAccessException {
        return div(val1, val2, scale, RoundingMode.HALF_UP);
    }

    /**
     * 提供精確的除法運算方法divide
     *
     * @param val1       被除數
     * @param val2       除數
     * @param scale        精確範圍(小數點後幾位)
     * @param roundingMode 精確模式
     */
    public static String div(String val1, String val2, int scale, RoundingMode roundingMode)
            throws IllegalAccessException {
        // 若是精確範圍小於0,拋出異常信息
        if (scale < 0) {
            throw new IllegalAccessException("精確度不能小於0");
        }
        BigDecimal b1 = new BigDecimal(val1);
        BigDecimal b2 = new BigDecimal(val2);
        // roundingMode爲null,則不須要精確
        if (roundingMode != null) {
            return Double.toString(b1.divide(b2, scale, roundingMode).doubleValue());
        } else {
            return Double.toString(b1.divide(b2, 0).doubleValue());
        }
    }

    /**
     * 提供精確乘法運算的multiply方法
     *
     * @param val1 被乘數
     * @param val2 乘數
     * @return 兩個參數的積
     */
    public static String mul(String val1, String val2) {
        return mul(val1, val2, 0, 0);
    }

    /**
     * 提供精確乘法運算的multiply方法(默認四捨五入)
     *
     * @param val1 被乘數
     * @param val2 乘數
     * @param scale  精確範圍(小數點後幾位)
     */
    public static String mul(String val1, String val2, int scale) {
        return mul(val1, val2, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 提供精確乘法運算的multiply方法
     *
     * @param val1    被乘數
     * @param val2    乘數
     * @param scale     精確範圍(小數點後幾位)
     * @param roundMode 舍入模式
     */
    public static String mul(String val1, String val2, int scale, int roundMode) {
        BigDecimal b1 = new BigDecimal(val1);
        BigDecimal b2 = new BigDecimal(val2);
        BigDecimal result = b1.multiply(b2);
        // mode爲0,則不須要精確
        if (roundMode != 0) {
            result = result.setScale(scale, roundMode);
        }
        return result.toString();
    }

    /**
     * 比較大小 :返回較大的那個
     *
     * @param val1 v1
     * @param val2 v2
     */
    public static String max(String val1, String val2) {
        BigDecimal b1 = new BigDecimal(val1);
        BigDecimal b2 = new BigDecimal(val2);
        return Double.toString(b1.max(b2).doubleValue());
    }

    /**
     * 比較大小 :返回較小的那個
     *
     * @param val1 v1
     * @param val2 v2
     */
    public static String min(String val1, String val2) {
        BigDecimal b1 = new BigDecimal(val1);
        BigDecimal b2 = new BigDecimal(val2);
        return Double.toString(b1.min(b2).doubleValue());
    }

    /**
     * 比較大小
     * if(r==0) v1等於v2
     * if(r==1) v1大於v2
     * if(r==-1) v1小於v2
     *
     * @param val1    v1
     * @param val2    v2
     * @param scale     精確範圍
     * @param roundMode 舍入模式
     */
    public static int compare(String val1, String val2, int scale, int roundMode) {
        BigDecimal b1 = new BigDecimal(val1);
        BigDecimal b2 = new BigDecimal(val2);
        BigDecimal result = b1.subtract(b2);
        // mode爲0,則不須要精確
        if (roundMode != 0) {
            result = result.setScale(scale, roundMode);
        }
        return result.compareTo(BigDecimal.ZERO);
    }

    public static void main(String[] args) throws IllegalAccessException {
        System.out.println(add("10", "5"));
        System.out.println(sub("10", "5"));
        System.out.println(mul("10", "5"));
        System.out.println(div("10", "5"));
        System.out.println(compare("-10", "5", 2, BigDecimal.ROUND_HALF_UP));
        System.out.println(max("10", "5"));
        System.out.println(min("10", "5"));

        BigDecimal v1 = new BigDecimal("-1");
        BigDecimal v2 = new BigDecimal("3");
        int r = v1.compareTo(v2);
        System.out.println(r);
    }
}
相關文章
相關標籤/搜索