为什么Math.floor(1.23456789 * 1e8)/ 1e8)返回1.23456788

时间:2017-05-09 22:36:24

标签: java decimalformat

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

返回:

  

1.23456788

考虑到这一点,这很奇怪:

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

返回:

  

1.23456789

以及

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

返回:

  

1.23456799

知道为什么会这样,以及如何避免它?

3 个答案:

答案 0 :(得分:3)

正如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

答案 1 :(得分:2)

浮点运算是不精确的。在浮点精度有限的情况下,转换为二进制和返回十进制可能会导致问题(因为二进制不能完美地表示小数,反之亦然)。

您可以使用BigDecimal来获得完美的数学,但速度要慢得多。如果你要做很多计算,这只是值得注意的。

修改: Here's a BigDecimal tutorial.

答案 2 :(得分:1)

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

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

文字1.23456789也不例外。它可以整齐地存储在两个可以存储为double的数字之间,并且这两个double数字的确切值是 1.2345678899999998900938180668163113296031951904296875 1.23456789000000011213842299184761941432952880859375 。规则是选择这两个数字越接近,因此文字1.23456789存储为 1.2345678899999998900938180668163113296031951904296875

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

文字1E9可以完全存储为double,因此第二个示例中的乘法为 1.2345678899999998900938180668163113296031951904296875 1000000000 ,其中当然是 1234567889.9999998900938180668163113296031951904296875 。此号码不能完全按double存储。距离它最近的double 1234567889.9999997615814208984375 ,最接近它的两倍是 1234567890 。但是这一次,它上方的double更接近,因此Java表达式1.23456789 * 1E9的值恰好是 1234567890 ,而Math.floor方法没有改变。

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

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

并且数字完全表示。