double精度踩坑以及总结的实践
double运算不精确
项目中的运算是这么个流程
82.0 / 10 => 8.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 | double k = 1.1 + 0.1; |
当然验证的几个都是正确的。
问题也很明显,那个setScale参数我是写死的,都是4。
当精度比4小或者比4大都会出现问题1
2
3
4
5
6
7
8double 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 | double k = 8.2 * 100000000000L; |
似乎找到了完美的解决方案
但是其实我们上面的假设都是基于一个假设,那就是我们的计算肯定会出现精度问题,并且肯定是有小数的
1 | double k = 8900000000000L; |
如果是这种情况呢?
那么我们会得到9.0E12
结论
别想了,除非你是要比较两个数的大小,可以不用BigDecimal,把两个数相减和一个0.000001比较就行
其他想使用最后结果的,还是乖乖的从头BigDecimal到尾吧。