Math.floor(1.23456789 * 1e8) / 1e8)
返回:
1.23456788
考虑到这一点,这很奇怪:
Math.floor(1.23456789 * 1e9) / 1e9)
返回:
1.23456789
以及
Math.floor(1.23456799 * 1e8) / 1e8)
返回:
1.23456799
知道为什么会这样,以及如何避免它?
答案 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
来获得完美的数学,但速度要慢得多。如果你要做很多计算,这只是值得注意的。
答案 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);
并且数字完全表示。