BigDecimal 精度问题
程序员文章站
2022-07-15 12:28:32
...
今天在做一个需求,遇到了一点问题,模拟代码如下:
// 小计
BigDecimal subtotal = new BigDecimal(1000000);
// 变更后数量
BigDecimal afterChangeTotal = new BigDecimal(300001);
MathContext mathContext = new MathContext(3, RoundingMode.UP);
BigDecimal result = afterChangeTotal.subtract(subtotal.multiply(new BigDecimal(0.3), mathContext)).divide(subtotal, mathContext);
BigDecimal deductedAdvance = new BigDecimal(0);
if(result.compareTo(deductedAdvance) > 0){
BigDecimal multiply = result.multiply(new BigDecimal(2), mathContext);
BigDecimal multiply2 = subtotal.multiply(new BigDecimal(0.1));
deductedAdvance = multiply.multiply(multiply2, mathContext);
}
System.out.println(deductedAdvance.toString());
然后我发现 这一行算出来精度怎么都不对
BigDecimal multiply2 = subtotal.multiply(new BigDecimal(0.1));
运行时值为这么大,照理说1000000 * 0.1 就是整整 100000; 怎么会带上这么多小数。
然后我想这给他做了格式化,没想到格式化了更糟糕...
我开始觉得我哪里写的有问题了,然后我把代码分开:
BigDecimal multiply2 = subtotal.multiply(bigDecimal);
改为:
BigDecimal bigDecimal = new BigDecimal(0.1);
BigDecimal multiply2 = subtotal.multiply(bigDecimal);
然后查看bigDecimal 这个值是多少最终发现原来是new 出来的这个BigDecimal的问题,然后我查阅了下JDK API帮助文档,介绍如下:
BigDecimal
public BigDecimal(double val)将 double 转换为 BigDecimal,后者是 double 的二进制浮点值准确的十进制表示形式。返回的 BigDecimal 的标度
是使 (10scale × val) 为整数的最小值。
注:
此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),
但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,
不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。 另一方面,String 构造方法是完全可预知的:
写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好 等于预期的 0.1。因此,比较而言,通常建议优先使用 String 构造方法。
当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double) 方法,
然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法。
参数:
val - 要转换为 BigDecimal 的 double 值。
抛出:
NumberFormatException - 如果 val 为无穷大或 NaN。
从API文档介绍中可以知道,这个double的构造方法他可能无法正确的构建BigDecimal, 如果需要构建建议使用带String的构造方法也可以使用BigDecimal.valueOf(double) 方法来构建, 方法内容如下:
public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}
其实看看可以看到,他也是把double数值先转成String, 然后使用带String的构造器创建对象。
总结:
构建BigDecimal时尽量使用下面两种方法构建,以免发生一些不必要,或难以想象的错误。
public BigDecimal(String val) ;
public static BigDecimal valueOf(double val);
上一篇: BigDecimal类的介绍和使用
下一篇: BigDecimal 精度问题