• 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);
        }
    }
  • 相关阅读:
    AtomicInteger
    Android利用ViewPager实现滑动广告板
    modelsim实用教程--前言
    信号的抽取和插值
    FPGA与simulink联合实时环路系列——实验三 按键key
    FPGA与simulink联合实时环路系列——实验二LED
    FPGA与simulink联合实时环路系列——实验一 测试
    FPGA与simulink联合实时环路系列—开篇
    格雷码原理与Verilog实现
    基于FPGA的飞机的小游戏
  • 原文地址:https://www.cnblogs.com/jockming/p/12233088.html
Copyright © 2020-2023  润新知