gpt4 book ai didi

java - 在加法之前将 double 转换为 BigDecimal 是否足以保留原始精度?

转载 作者:行者123 更新时间:2023-12-02 01:43:50 28 4
gpt4 key购买 nike

我们正在解决与数字精度相关的错误。我们的系统收集一些数字并给出它们的总和。问题是系统不保留数字精度,例如300.7 + 400.9 = 701.599...,而预期结果为 701.6。精度应该适应输入值,因此我们不能将结果舍入到固定精度。

问题很明显,我们使用 double 作为值,并且加法会累积十进制值的二进制表示形式的误差。

数据路径如下:

  1. XML 文件,类型 xsd:decimal
  2. 解析为 java 原始 double 型。 15 位小数应该足够了,我们期望值总共不超过 10 位,5 位小数。
  3. 存储到 DB MySql 5.5, type double
  4. 通过 Hibernate 加载到 JPA 实体中,即仍然原始的 double
  5. 对这些值进行求和
  6. 将总和打印到另一个 XML 文件中

现在,我认为最佳解决方案是将所有内容转换为十进制格式。毫不奇怪,人们面临着采用最便宜的解决方案的压力。事实证明,将 double 转换为 BigDecimal在添加几个数字之前,对于以下示例中的情况 B 有效:

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无法完成上述工作。

最佳答案

如果无法避免解析为原始 double 或存储为 double,则应尽早转换为 BigDecimal

double 不能精确表示小数。 double x = 7.3; 中的值永远不会恰好是 7.3,而是非常接近它的值,从右侧第 16 位左右可见差异(给出 50 位小数左右) )。不要被打印可能精确地给出“7.3”这一事实所误导,因为打印已经进行了某种舍入,并且没有准确地显示数字。

如果您使用数字进行大量计算,微小的差异最终会累积起来,直到超出您的容忍度。因此,在需要小数的计算中使用 double 确实是一个定时炸弹。

[...] we expect values no longer than 10 digits total, 5 fraction digits.

我认为这个断言意味着您处理的所有数字都必须是 0.00001 的精确倍数,没有任何其他数字。您可以使用

将 double 转换为此类 BigDecimals
new BigDecimal.valueOf(Math.round(doubleVal * 100000), 5)

这将为您提供具有 5 个小数位的数字的精确表示,即最接近输入 doubleVal 的 5 位小数位。这样您就可以纠正 doubleVal 和您最初想要的十进制数之间的微小差异。

如果您只是使用 BigDecimal.valueOf(double val),您将遍历您正在使用的 double 的字符串表示形式,这不能保证它是您想要的。它取决于 Double 类内部的舍入过程,该过程试图用最合理的十进制位数表示 7.3 的 double 近似值(可能是 7.30000000000000123456789123456789125)。它恰好导致“7.3”(并且,对开发人员来说,它经常匹配“预期”字符串),而不是“7.300000000000001”或“7.3000000000000012”,这对我来说似乎同样合理。

这就是为什么我建议不要依赖四舍五入,而是通过小数点移动 5 位自行进行舍入,然后四舍五入到最接近的 long,并构造一个缩小 5 位小数的 BigDecimal。这可以保证您获得(最多)5 位小数的精确值。

然后使用 BigDecimals 进行计算(如有必要,使用适当的 MathContext 进行舍入)。

当您最终必须将数字存储为 double 时,请使用BigDecimal.doubleValue()。生成的 double 值将足够接近小数,上述转换肯定会给您提供与之前相同的 BigDecimal (除非您有非常大的数字,例如小数点前 10 位数字 - 您会迷失 无论如何,双)。

附注仅当小数分数与您相关时,请务必使用BigDecimal - 英国先令货币有时由 12 便士组成。将小数磅表示为 BigDecimal 会比使用 double 带来更严重的灾难。

关于java - 在加法之前将 double 转换为 BigDecimal 是否足以保留原始精度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54011168/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com