Java精确计算

Posted 滴水穿石

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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);
    }
}

 

以上是关于Java精确计算的主要内容,如果未能解决你的问题,请参考以下文章

从JVM的角度看JAVA代码--代码优化

Java精确计算

BigDecimal做精确计算

JAVA中精确计算金额BigDecimal

java精确计算工具类

Java中小数精确计算