【问题标题】:Why does Math.floor(1.23456789 * 1e8) / 1e8) return 1.23456788为什么 Math.floor(1.23456789 * 1e8) / 1e8) 返回 1.23456788
【发布时间】:2017-05-09 22:36:23
【问题描述】:
Math.floor(1.23456789 * 1e8) / 1e8)

返回:

1.23456788

考虑到这一点很奇怪:

Math.floor(1.23456789 * 1e9) / 1e9)

返回:

1.23456789

还有

Math.floor(1.23456799 * 1e8) / 1e8)

返回:

1.23456799

知道为什么会发生这种情况以及如何避免吗?

【问题讨论】:

标签: java decimalformat


【解决方案1】:

正如 Daniel Centore 所回答的,双精度值是不精确的。这是双精度数字的实际值(至 20 位)的列表。显示的是 1.23456789 的两个最接近的编码;第一个更接近。乘以 1e8 时,乘法不会四舍五入。当乘以 1e9 时,乘法四舍五入到一个精确的整数值。

1.23456789     => 1.2345678899999998901 hex 3ff3c0ca4283de1b
1.23456789     => 1.2345678900000001121 hex 3ff3c0ca4283de1c
1.23456789*1e8 => 123456788.99999998510 hex 419d6f3453ffffff
1.23456789*1e9 => 1234567890.0000000000 hex 41d26580b4800000

【讨论】:

    【解决方案2】:

    浮点运算不精确。除了浮点精度有限这一事实之外,转换为二进制并返回十进制可能会导致问题(因为二进制不能完美地表示小数,反之亦然)。

    您可以使用BigDecimal 获得完美的数学运算,但速度要慢得多。只有在进行大量计算时才会注意到这一点。

    编辑: Here's a BigDecimal tutorial.

    【讨论】:

      【解决方案3】:

      Java double 类型(几乎)符合称为 IEEE-754 的浮点数国际标准。可以用这种类型表示的数字都有一个共同点——它们在二进制中的表示形式在最多 53 个有效数字之后终止。

      大多数具有终止十进制表示的数字没有终止二进制表示,这意味着没有 double 可以准确地存储它们。当您在 Java 中编写 double 文字时,存储在 double 中的值通常不是您在文字中写入的数字,而是最接近的可用 double

      文字1.23456789 也不例外。它在整齐地落在两个可以存储的数字之间,它可以存储为double,这两者double的确切值 1.2345938899999989042960395904296999959089689395089089375/ca]规则是选择这两个数字中更接近的一个,因此文字 1.23456789 存储为 1.2345678899999998900938180668163113296031951904296875

      文字 1E8 可以完全存储为 double,因此第一个示例中的乘法是 1.2345678899999998900938180668163113296031951904296875 乘以 100000000,当然是 >123456788.99999998900938180668163113296031951904296875。此号码不能完全存储为double。它下面最近的double123456788.99999998509883880615234375,而它上面最近的double 正好是123456789。但是它下面的double更接近,所以Java表达式1.23456789 * 1E8的值实际上是123456788.99999998509883880615234375。当你对这个数字应用Math.floor 方法时,结果正好是123456788

      文字 1E9 可以完全存储为 double,因此第二个示例中的乘法是 1.2345678899999998900938180668163113296031951904296875 乘以 1000000000,当然是 >1234567889.9999998900938180668163113296031951904296875。此号码不能完全存储为double。它下面最近的double1234567889.9999997615814208984375,它上面最近的双精度是1234567890。但这一次,它上面的double更接近,所以Java表达式1.23456789 * 1E9的值正好是1234567890Math.floor方法没有改变。

      您问题的第二部分是如何避免这种情况。好吧,如果你想进行精确的计算,涉及带有终止十进制表示的数字,你不能将它们存储在 double 变量中。相反,您可以使用 BigDecimal 类,它可以让您做这样的事情

      BigDecimal a = new BigDecimal("1.23456789");
      BigDecimal b = new BigDecimal("100000000");
      BigDecimal product = a.multiply(b);
      

      而且数字是准确表示的。

      【讨论】:

      • 感谢您的分析,这是有道理的。我试图避免 BigDecimal 虽然它真的很慢......
      猜你喜欢
      • 2014-11-01
      • 2010-10-05
      • 2015-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多