51

我有以下代码:

Double i=17.31;
long j=(long) (i*100);
System.out.println(j);

开/关:1730 //Expected:1731

Double i=17.33;
long j=(long) (i*100);
System.out.println(j);

开/关:1732 //Expected:1733

Double i=17.32;
long j=(long) (i*100);
System.out.println(j);

开/关:1732 //Expected:1732{As expected}

Double i=15.33;
long j=(long) (i*100);
System.out.println(j);

开/关:1533 //Expected:1533{as Expected}

我已经尝试谷歌但无法找到原因。如果问题微不足道,我很抱歉。

4

7 回答 7

78

似乎没有一个答案涉及为什么 17.32表现不同。

1. 为什么会发生

17.32您在和之间看到的行为差异17.33 & 17.31是由于 IEEE-754舍入规则造成的。

应用的舍入规则:来自Java™ 虚拟机规范 §2.8.1

Java 虚拟机的舍入操作始终使用 IEEE 754 舍入到最接近模式。不精确的结果被四舍五入到最接近的可表示值,并与具有零最低有效位的值相匹配。这是 IEEE 754 默认模式。Java 虚拟机没有给出任何改变浮点舍入模式的方法


2. 你的情况:

双精度为:(1 个符号位 + 11 个指数位 + 52 个小数位 = 64 位)。以下四舍五入后的内部表示

             1 [63]      11 [62-52]           52 [51-00]
              Sign        Exponent             Fraction

17.31 -->    0 (+)       10000000011 (+4)     1.0001010011110101110000101000111101011100001010001111
17.32 -->    0 (+)       10000000011 (+4)     1.0001010100011110101110000101000111101011100001010010 //rounded up
17.33 -->    0 (+)       10000000011 (+4)     1.0001010101000111101011100001010001111010111000010100

3. 内部表示(证明):

17.31:(尾数比较)

Actual:   1.00010100111101011100001010001111010111000010100011110...
Internal: 1.0001010011110101110000101000111101011100001010001111

17.32:(尾数比较)

Actual:   1.00010101000111101011100001010001111010111000010100011... 
Internal: 1.0001010100011110101110000101000111101011100001010010    //round-up!

17.33:(尾数比较)

Actual:   1.00010101010001111010111000010100011110101110000101000...
Internal: 1.0001010101000111101011100001010001111010111000010100

4. 转换回十进制:

17.31 ->  17.309999999999998721023075631819665431976318359375...
17.32 ->  17.32000000000000028421709430404007434844970703125... //(was rounded up)
17.33 ->  17.3299999999999982946974341757595539093017578125...

IEEE-754 分析工具

5.投长

编辑:正如@Jeppe Stig Nielsen 所说,在您的乘法步骤中还有一个因素在起作用。FP乘法 ( Reference ) 步骤的结果会自行向最近舍入。这会改变结果是否符合预期,但原因仍然与上述完全相同。

最后,由于 cast (long),会发生截断,并留下您看到的结果。(1730, 1732, 1732)

缩小原始转换:Java™ 语言规范 §5.1.3

如果浮点数不是无穷大,则浮点值四舍五入为整数值 V,使用 IEEE 754 向零舍入模式向零舍入

于 2012-11-20T10:28:15.030 回答
22

double值不是表示为 17.31,而是表示为 17.309999999999999。这就是为什么当你将它乘以 100 时,你会得到 1730.99999999999999999。转换为Long您的double值后,将被截断为零。所以你得到1730。

于 2012-11-20T06:44:56.157 回答
5

如前所述,这是由于浮点精度非常小。

这可以通过使用 Math.round() 命令来解决,如下所示:

long j=Math.round(i*100);

这将允许程序通过不使用地板运算来补偿使用浮点计算继承的非常小的错误,就像默认(长)那样。

于 2012-11-20T10:56:24.940 回答
5

Cthulhu 和 svz 的答案是正确的。如果要将双精度数乘以 100 并避免浮点舍入错误,则可以使用在每次乘法后将Math.round()结果舍入到最接近的值:long

Double i=17.31;
long j=Math.round(i*100);
System.out.println(j);

在处理非常大(或负)的双精度时,这仍然会有浮点错误。double 的绝对值越大,它与 Java 可以表示的下一个 double 之间的差异就越大。在某个点之后,连续的双精度数相距超过一个整数,并且传统的四舍五入将无法消除差异。但是,对于您发布的示例,这应该可以工作。

于 2012-11-20T20:54:22.837 回答
4

它与内部表示有关。如果你看一下第一种情况下的 i*100,你会看到它是 1730.9999999999998。演员只会删除点之后的部分(截断)。

于 2012-11-20T06:45:15.373 回答
2

对于类似的值, 字符串值 = 1074.6

使用下面的代码

double amount = Double.parseDouble(value);

String roundedValue = String.valueOf(Math.round(amount * 100))

你可以得到结果107460

于 2021-01-07T14:04:24.443 回答
0

当你进行这种长时间的转换时,它就是底线。您的 17.31 实际上可能是 17.30999999999,这就是它导致 1730 而不是 1731 的原因。

使用 i = i * 100,然后 i.longValue() 将解决问题。

于 2012-11-20T06:47:26.617 回答