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时,尽量使用字符串传参