【发布时间】:2019-01-02 18:11:13
【问题描述】:
我们正在解决与数值精度相关的错误。我们的系统收集一些数字并吐出它们的总和。 问题是 系统不保留数字精度, 例如300.7 + 400.9 = 701.599...,而预期结果为 701.6。精度应该适应输入值,所以我们不能只将结果四舍五入到固定精度。
问题很明显,我们使用双精度值,加法累加了十进制值的二进制表示的误差。
数据的路径如下:
- XML 文件,类型 xsd:decimal
- 解析成 java 原始双精度。它的 15 位小数应该足够了,我们希望值总计不超过 10 位,5 个小数位。
- 存入DB MySql 5.5, type double
- 通过 Hibernate 加载到 JPA 实体中,即仍然是原始的 double
- 这些值的总和
- 将总和打印到另一个 XML 文件中
现在,我假设最佳解决方案是将所有内容都转换为十进制格式。毫不奇怪,采用最便宜的解决方案是有压力的。事实证明,在以下示例中的情况 B 中,在添加几个数字之前将双精度数转换为 BigDecimal 有效:
import java.math.BigDecimal;
public class Arithmetic {
public static void main(String[] args) {
double a = 0.3;
double b = -0.2;
// A
System.out.println(a + b);//0.09999999999999998
// B
System.out.println(BigDecimal.valueOf(a).add(BigDecimal.valueOf(b)));//0.1
// C
System.out.println(new BigDecimal(a).add(new BigDecimal(b)));//0.099999999999999977795539507496869191527366638183593750
}
}
更多信息: Why do we need to convert the double into a string, before we can convert it into a BigDecimal? Unpredictability of the BigDecimal(double) constructor
我担心这样的解决方法会成为一颗定时炸弹。 首先,我不太确定这种算法是否适用于所有情况。 其次,仍然存在一些风险,将来有人可能会实施一些更改并将 B 更改为 C,因为这个陷阱远非显而易见,甚至单元测试也可能无法揭示错误。
我愿意接受第二点,但问题是:这种解决方法会提供正确的结果吗?会不会有某种情况以某种方式
Double.valueOf("12345.12345").toString().equals("12345.12345")
是假的吗?鉴于根据 javadoc,Double.toString 只打印唯一表示底层 double 值所需的数字,所以当再次解析时,它会给出相同的 double 值?对于我只需要添加数字并使用这种神奇的Double.toString(Double d) 方法打印总和的用例来说,这还不够吗?需要明确的是,我确实更喜欢我认为的干净的解决方案,在任何地方都使用 BigDecimal,但我有点缺乏出售它的论据,我的意思是理想情况下在添加之前转换为 BigDecimal 的示例未能完成上述工作。
【问题讨论】:
-
"2. 解析成 java 原语双精度" 当你这样做的时候,你已经失去了精度。立即将其解析为
BigDecimal。 -
如果您需要保证精度,
double不应用于处理的任何部分。 -
在将数据解析为
double的那一刻,您就会失去精确度。double无法精确地包含值0.2。它根本无法以二进制格式表示。但它是“正确”打印的,因为字符串转换考虑到了这一点并对其进行了四舍五入。这就是为什么double的直接转换是不可预测的。 -
他们说的:一碰
double,它就坏了。仅限 BigDecimals,而且只有当您直接从字符串转换时才会这样做:BigDecimal.valueOf("0.3"),而不是BigDecimal.valueOf(0.3)。 -
是的,浮点格式在内部不能精确地表示十进制值。无论如何,双精度足以容纳一个 15 位数字,这样当例如从 String 解析为 double 的 10 位值使用 Double.toString 中的特殊舍入算法再次打印到 String,我应该产生与我首先解析 double 相同的值,对吗?然后这个 String 用于构造 BigDecimal,BigDecimal 又用于数学运算。我错过了其他一些陷阱?
标签: java double precision bigdecimal