BigDecimal精确进行数学运算

问题

假设现在有个计算公式如下

public static void getResult(int total, int pbd, int ag, int num) {
        double cl = 10 * Math.log10(pbd);
        double txg = 10 * Math.log10(num);
        double result = total - cl + ag + txg;

        logger.info(String.valueOf(result));
    }

main函数中调用测试

public static void main(String[] args) {
        int pbd = 20;
        int ag = -5;
        int num = 2;

        getResult(23, pbd, ag, num);
        getResult(22, pbd, ag, num);
        getResult(21, pbd, ag, num);
        getResult(20, pbd, ag, num);
        getResult(19, pbd, ag, num);
        getResult(18, pbd, ag, num);
        getResult(17, pbd, ag, num);
        getResult(16, pbd, ag, num);
        
    }

运行结果如下

16:29:04.997 [main] INFO com.demo.BigDecimalTest - 8.0
16:29:05.009 [main] INFO com.demo.BigDecimalTest - 7.0
16:29:05.009 [main] INFO com.demo.BigDecimalTest - 6.0
16:29:05.009 [main] INFO com.demo.BigDecimalTest - 5.0
16:29:05.009 [main] INFO com.demo.BigDecimalTest - 3.9999999999999996
16:29:05.009 [main] INFO com.demo.BigDecimalTest - 2.9999999999999996
16:29:05.009 [main] INFO com.demo.BigDecimalTest - 1.9999999999999996
16:29:05.009 [main] INFO com.demo.BigDecimalTest - 0.9999999999999996

可以发现,只有total参数按照1个步长递减,后面的参数没有变化,这很明显有些参数计算是不正确的

pbd和num的对数分别计算出来

logger.info(String.valueOf(10 * Math.log10(20)));
logger.info(String.valueOf(10 * Math.log10(2)));
16:33:18.017 [main] INFO com.demo.BigDecimalTest - 13.010299956639813
16:33:18.017 [main] INFO com.demo.BigDecimalTest - 3.010299956639812

那么在total为20和19时的计算公式如下

20-13.010299956639813-5+3.010299956639812 = 5.0
19-13.010299956639813-5+3.010299956639812 = 3.9999999999999996

这两个结果其实都不准确,但为19的时候相对准确度高一点,为20的时候,这个结果肯定不可能刚好为整数,因为小数部分是不同的。

在验证一个问题

logger.info(String.valueOf(19-13.0103 -5 + 3.0103));

这个结果肉眼计算应该是4,但是java运行结果却为3.999999999999999

16:44:01.118 [main] INFO com.demo.BigDecimalTest - 3.999999999999999

通过以上现象说明,如果包含小数的运算,直接用java中加减符号是无法做到准确的。

BigDecimal解决问题

一、将上面的问题改为BigDecimal进行计算

public static void getResult2(int total, int pbd, int ag, int num) {
        double cl = 10 * Math.log10(pbd);
        double txg = 10 * Math.log10(num);

        BigDecimal result = new BigDecimal(total).subtract(new BigDecimal(cl)).add(new BigDecimal(ag))
                .add(new BigDecimal(txg));

        logger.info(String.valueOf(result.doubleValue()));
    }

结果如下

16:49:08.445 [main] INFO com.demo.BigDecimalTest - 8.0
16:49:08.457 [main] INFO com.demo.BigDecimalTest - 7.0
16:49:08.458 [main] INFO com.demo.BigDecimalTest - 6.0
16:49:08.458 [main] INFO com.demo.BigDecimalTest - 5.0
16:49:08.458 [main] INFO com.demo.BigDecimalTest - 3.9999999999999996
16:49:08.458 [main] INFO com.demo.BigDecimalTest - 2.9999999999999996
16:49:08.458 [main] INFO com.demo.BigDecimalTest - 1.9999999999999996
16:49:08.458 [main] INFO com.demo.BigDecimalTest - 0.9999999999999996

问题并没有得到解决

二、结果使用toString输出

public static void getResult3(int total, int pbd, int ag, int num) {
        double cl = 10 * Math.log10(pbd);
        double txg = 10 * Math.log10(num);

        BigDecimal result = new BigDecimal(total).subtract(new BigDecimal(cl)).add(new BigDecimal(ag))
                .add(new BigDecimal(txg));

        logger.info(result.toString());
    }
16:51:03.310 [main] INFO com.demo.BigDecimalTest - 7.999999999999999555910790149937383830547332763671875
16:51:03.324 [main] INFO com.demo.BigDecimalTest - 6.999999999999999555910790149937383830547332763671875
16:51:03.324 [main] INFO com.demo.BigDecimalTest - 5.999999999999999555910790149937383830547332763671875
16:51:03.324 [main] INFO com.demo.BigDecimalTest - 4.999999999999999555910790149937383830547332763671875
16:51:03.324 [main] INFO com.demo.BigDecimalTest - 3.999999999999999555910790149937383830547332763671875
16:51:03.324 [main] INFO com.demo.BigDecimalTest - 2.999999999999999555910790149937383830547332763671875
16:51:03.324 [main] INFO com.demo.BigDecimalTest - 1.999999999999999555910790149937383830547332763671875
16:51:03.325 [main] INFO com.demo.BigDecimalTest - 0.999999999999999555910790149937383830547332763671875

结果正确,而且精确度很高,保留了小数点后51位

三、将参数转换为字符串传递到BigDecimal构造方法中,使用doubleValue输出

public static void getResult4(int total, int pbd, int ag, int num) {
        double cl = 10 * Math.log10(pbd);
        double txg = 10 * Math.log10(num);

        BigDecimal result = new BigDecimal(String.valueOf(total)).subtract(new BigDecimal(String.valueOf(cl)))
                .add(new BigDecimal(String.valueOf(ag))).add(new BigDecimal(String.valueOf(txg)));

        logger.info(String.valueOf(result.doubleValue()));
    }
16:54:10.190 [main] INFO com.demo.BigDecimalTest - 7.999999999999999
16:54:10.202 [main] INFO com.demo.BigDecimalTest - 6.999999999999999
16:54:10.202 [main] INFO com.demo.BigDecimalTest - 5.999999999999999
16:54:10.202 [main] INFO com.demo.BigDecimalTest - 4.999999999999999
16:54:10.202 [main] INFO com.demo.BigDecimalTest - 3.999999999999999
16:54:10.202 [main] INFO com.demo.BigDecimalTest - 2.999999999999999
16:54:10.202 [main] INFO com.demo.BigDecimalTest - 1.999999999999999
16:54:10.202 [main] INFO com.demo.BigDecimalTest - 0.999999999999999

结果正确,保留了小数点后15位,对一般的场景,保留15位的精度应该已经满足要求了

四、在上一步基础上,输出时使用toString

public static void getResult5(int total, int pbd, int ag, int num) {
        double cl = 10 * Math.log10(pbd);
        double txg = 10 * Math.log10(num);

        BigDecimal result = new BigDecimal(String.valueOf(total)).subtract(new BigDecimal(String.valueOf(cl)))
                .add(new BigDecimal(String.valueOf(ag))).add(new BigDecimal(String.valueOf(txg)));

        logger.info(result.toString());
    }
16:57:59.354 [main] INFO com.demo.BigDecimalTest - 7.999999999999999
16:57:59.367 [main] INFO com.demo.BigDecimalTest - 6.999999999999999
16:57:59.367 [main] INFO com.demo.BigDecimalTest - 5.999999999999999
16:57:59.367 [main] INFO com.demo.BigDecimalTest - 4.999999999999999
16:57:59.367 [main] INFO com.demo.BigDecimalTest - 3.999999999999999
16:57:59.367 [main] INFO com.demo.BigDecimalTest - 2.999999999999999
16:57:59.368 [main] INFO com.demo.BigDecimalTest - 1.999999999999999
16:57:59.368 [main] INFO com.demo.BigDecimalTest - 0.999999999999999

结果和上一步使用doubleValue相同。

 

1、如果有小数计算,尽量使用BigDecimal

2、使用BigDecimal时,尽量使用字符串传参

posted @ 2020-07-25 17:00  大坑水滴  阅读(468)  评论(0编辑  收藏  举报