double精度踩坑

double精度踩坑以及总结的实践

double运算不精确

项目中的运算是这么个流程

  1. 82.0 / 10 => 8.2 第一步是没有什么问题的
  2. 8.2 * 100 => 819.9999999999999 这一步出现问题了

然后我去结果进行了int的取整操作,得到了819。

当然我考虑了精度问题,但是是对819.9999999999999套上了BigDecimal,取值的时候选择了向下取整,结果还是819。

解决方案

解决方案还是BigDemical,这个是没有办法的。

但是BigDemical到底要用到什么程度呢

比如上面的改成

1
BigDecimal.valueOf(82).divide(BigDecimal.valueOf(10)).multiply(BigDecimal.valueOf(100)).intValue()

当然是没有问题的,但是未免太麻烦。

对比几个精度缺失的问题,我发现都是最后的一位不精确,相差的都是0.00...X的位数
所以我觉得可以在最后一个double进行BigDecimal,设置精度,然后进行四舍五入取整。

1
2
3
4
5
6
7
8
9
10
11
double k = 1.1 + 0.1;
System.out.println(k); => 1.2000000000000002
System.out.println(BigDecimal.valueOf(k).setScale(4, RoundingMode.HALF_UP).doubleValue()); => 1.2

double k = 82.0 / 10 * 100;
System.out.println(k); => 819.9999999999999
System.out.println(BigDecimal.valueOf(k).setScale(4, RoundingMode.HALF_UP).doubleValue()); => 820.0

double k = 0.06 + 0.01;
System.out.println(k); => 0.06999999999999999
System.out.println(BigDecimal.valueOf(k).setScale(4, RoundingMode.HALF_UP).doubleValue()); => 0.07

当然验证的几个都是正确的。

问题也很明显,那个setScale参数我是写死的,都是4。
当精度比4小或者比4大都会出现问题

1
2
3
4
5
6
7
8
double k = 0.0000006 + 0.0000001;
System.out.println(BigDecimal.valueOf(k).setScale(4, RoundingMode.HALF_UP).doubleValue()); => 0.0
System.out.println(BigDecimal.valueOf(k).setScale(10, RoundingMode.HALF_UP).doubleValue()); => 7.0E-7

double k = 8.2 * 100000000000L;
System.out.println(k); => 8.199999999999999E11
System.out.println(BigDecimal.valueOf(k).setScale(4, RoundingMode.HALF_UP).doubleValue()); => 8.199999999999999E11
System.out.println(BigDecimal.valueOf(k).setScale(1, RoundingMode.HALF_UP).doubleValue()); => 8.2E11

其实还是要了解scale代表着什么意思
是小数点后的精度的问题

但是如果数字过小,那么scale就要设置的大一点
就比如0.0000006 + 0.0000001其实已经到小数点后7位了,设置4位的肯定不行

但是作为Double,有效位数是16,如果小数点前的数字过多,那么其实scale设置得大也没用
8.2 * 100000000000L的结果是81999_9999_999.9999这里即使我们设置到scale=4,后面其实已经没有数字了,所以得到的结果还是81999..

那么似乎好像我们拿到scale就行了,把scale设置的比原始的小一点

1
2
3
4
5
6
7
8
9
double k = 8.2 * 100000000000L;
System.out.println(k); => 8.199999999999999E11
BigDecimal decimal = BigDecimal.valueOf(k);
System.out.println(decimal.setScale(decimal.scale() - 1, RoundingMode.HALF_UP).doubleValue()); => 8.2E11

double k = 1.1 + 0.1;
System.out.println(k); => 1.2000000000000002
BigDecimal decimal = BigDecimal.valueOf(k);
System.out.println(decimal.setScale(decimal.scale() - 1, RoundingMode.HALF_UP).doubleValue()); => 1.2

似乎找到了完美的解决方案

但是其实我们上面的假设都是基于一个假设,那就是我们的计算肯定会出现精度问题,并且肯定是有小数的

1
2
3
4
5
double k = 8900000000000L;
System.out.println(k);
BigDecimal decimal = BigDecimal.valueOf(k);
System.out.println(decimal.scale());
System.out.println(decimal.setScale(decimal.scale() - 1, RoundingMode.HALF_UP).doubleValue());

如果是这种情况呢?
那么我们会得到9.0E12

结论

别想了,除非你是要比较两个数的大小,可以不用BigDecimal,把两个数相减和一个0.000001比较就行
其他想使用最后结果的,还是乖乖的从头BigDecimal到尾吧。