Java精確計算


簡介

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

因此計算得出的結果往往超出預期。

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

雖然我們可以通過四舍五入的方式來處理結果,但是這樣做就意味着存在着誤差。

 

場景分析

比如說下面這些計算,我知道結果應該是精確的數字,計算機並沒有計算出我們想要的結果。

/**
 * @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);
   }
}

 

計算結果:

0.060000000000000005
0.5700000000000001
20.299999999999997
0.32999999999999996

 

BigDecimal

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

 

四則運算

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定義了以下舍入模式,只有在做除法運算或四舍五入時才會用到舍入模式。

 

ROUND_CEILING

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

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);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM