【问题标题】:Multiplying two numbers using BigDecimal returns a wrong value使用 BigDecimal 将两个数字相乘会返回错误值
【发布时间】:2014-08-07 09:38:51
【问题描述】:

执行以下代码:

new BigDecimal(0.06 * 3).toString()

返回 0.179999999999999993338661852249060757458209991455078125 而不是 0.18

执行中

new BigDecimal(0.06).multiply(new BigDecimal(3)).toString() 

返回相同的结果。

这怎么可能?

【问题讨论】:

  • +1 - 虽然第一个代码的错误非常明显,但第二个代码的问题在编码过程中极容易被忽略,并且可能会在代码中一遍又一遍地引入细微的错误。我什至会说它应该能够在 IDE 中为它生成警告。
  • 考虑到引用 ",这个构造函数的结果可能有点不可预测。人们可能会假设用 Java 编写 new BigDecimal(0.1) 会创建一个 BigDecimal,这正是等于 0.1(未缩放的值为 1,比例为 1),但它实际上等于 0.1000000000000000055511151231257827021181583404541015625" - 来自 API 文档docs.oracle.com/javase/8/docs/api/java/math/…
  • @Marco13 如果 Oracle 文档明确指出它可能是不可预测的(因此令人困惑),我倾向于说在 SO 上进行问答会更合适,而且你已经只是给出了这个问题值得投票的另一个原因

标签: java math bigdecimal


【解决方案1】:

您不是使用BigDecimal 将两个数字相乘。您使用double 算术将它们相乘,并将结果传递给BigDecimal 构造函数。

你想要:

new BigDecimal("0.06").multiply(new BigDecimal("3")).toString()

请注意,您确实需要字符串中的值 - 否则您将使用 0.06 的 double 值,这不完全是 0.06...在开始之前您已经丢失了信息. (你并不真的需要 3 的字符串形式,但我这样做是为了保持一致性。)

例如:

System.out.println(new BigDecimal(0.06));

打印

0.059999999999999997779553950749686919152736663818359375

【讨论】:

  • 执行new BigDecimal(0.06).multiply(new BigDecimal(3)).toString()返回相同的结果
  • @Mark:这是由于一个单独的问题,我将在编辑中解释。
  • @Mark:出于所有正常原因,它不能完全表示为double。想想位模式是什么——这就像试图将 1/3 表示为精确的小数。
  • @JonSkeet 使用常量时没问题。但是当生产代码已经为您提供double 时,我觉得在将其解析回BigDecimal 之前将其格式化为String 是一种浪费...
  • @dotvav:你认为BigDecimal.valueOf 在做什么?它也将其转换为字符串 :) 但我认为,如果您将 double 转换为 BigDecimal,那么您很可能一开始就遇到了严重的问题,您应该重新审视您的设计。
【解决方案2】:

正如 Jon Skeet 在上面所写的,你得到 0.179999999999999993338661852249060757458209991455078125 而不是 0.18 的原因是因为 0.06 * 3 被计算为 IEEE 754 double,然后这个 double 值被转换为 BigDecimal

尽管0.06 在源代码中看起来很简单,但数字 0.06 并不能完全表示为 IEEE 754 double,因此 0.06 实际表示的是 0.06 的 近似值 等于至 0.059999999999999997779553950749686919152736663818359375。十进制表示的数字 0.06 不能精确表示,因为该数字等于二进制表示的 0b0.000011110101110000101(其中 bold 表示重复的数字序列)。计算机必须截断这个无限的二进制数字序列,得出 0.0599999... 的近似值。

正如我在my answer to Question regarding IEEE 754, 64 bits double? 中详述的,您可以使用ARIBAS'decode_float() 函数来确定浮点数的尾数和指数:

==> set_floatprec(double_float)。 -:64 ==> set_printbase(2)。 -:0y10 ==> decode_float(0.06)。 -:(0y11110101_11000010_10001111_01011100_00101000_11110101_11000010_10001111, -0y1000100) ==> set_printbase(10)。 -:10 ==> -0y1000100。 -:-68 ==> set_floatprec(128)。 -:128 ==> 1/2**4 + 1/2**5 + 1/2**6 + 1/2**7 + 1/2**9 + 1/2**11 + 1/2** 12 + 1/2**13 + 1/2**18 + 1/2**20。 -:0.11999_98855_59082_03125_00000_00000_00000_00

** 是 ARIBAS 中的幂运算)。

我们有 0.06 = Σ i = 0..∞0.11999988555908203125 / 21 + 20 × i

您可以在 Maxima 等计算机代数系统中评估该系列:

(%i1) sum ( 0.11999988555908203125 / 2 ^ (1 + 20 * i), i, 0, inf ), simpsum; (%o1) 0.06

http://maxima-online.org/?inc=r760264757

【讨论】:

    【解决方案3】:

    BigDecimal#valueOf 更好地合作:

    BigDecimal.valueOf(0.06).multiply(BigDecimal.valueOf(3))
    

    打印正确的结果0.18

    【讨论】:

    • +0 - 正如 Jon Skeet 和 Java API 文档所指出的那样,它将 a) 从文字中生成不精确的双精度值,b) 将数字转换为字符串,同时将其四舍五入,c ) 将字符串解析为 BigDecimal...因此,使用带引号的字符串作为提供给 BigDecimal 的小数文字通常是最好的选择。
    猜你喜欢
    • 1970-01-01
    • 2018-05-10
    • 1970-01-01
    • 2014-08-08
    • 1970-01-01
    • 2010-12-24
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    相关资源
    最近更新 更多