java-将double乘以100,然后强制转换为long给出错误的值

我有以下代码:

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

O / P:1533 //Expected:1533{as Expected}

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

O/P : 1533 //Expected:1533{as Expected}

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

O/P : 1533 //Expected:1533{as Expected}

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

O/P : 1533 //Expected:1533{as Expected}

我已经尝试过使用Google,但找不到原因。如果问题很简单,我们感到抱歉。

xyz asked 2020-08-01T12:45:04Z
6个解决方案
74 votes

似乎没有一个答案可以解释为什么(long)表现出不同。

1.为什么发生

您在(long)(1730, 1732, 1732)之间看到的行为差异是由于IEEE-754舍入规则。

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

Java虚拟机的舍入操作始终使用IEEE 754舍入到最近模式。 不精确的结果四舍五入到最接近的值 可表示的值,并且领带的值变为零 最低有效位。 这是IEEE 754的默认模式。 Java虚拟 机器不提供任何方法来更改浮点舍入 模式


2.您的情况:

Double是:(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:(Mantissa比较)

Actual:   1.00010100111101011100001010001111010111000010100011110...
Internal: 1.0001010011110101110000101000111101011100001010001111

17.32:(Mantissa比较)

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

17.33:(Mantissa比较)

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乘法(参考)步骤的结果将自身舍入为最接近的值。这种变化会产生预期的结果,而不会产生预期的结果,但是原因仍然与上述完全相同。

最后,由于强制转换(long),会发生截断,并为您提供看到的结果。 (1730, 1732, 1732)

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

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

Anirudh Ramanathan answered 2020-08-01T12:46:38Z
22 votes

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

svz answered 2020-08-01T12:46:58Z
5 votes

如已经说明的,这是由于非常小的浮点精度。

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

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

这将使程序可以补偿浮点计算所继承的很小的错误,而不必像默认值(长整数)那样使用下位运算。

PearsonArtPhoto answered 2020-08-01T12:47:27Z
4 votes

它与内部表示有关。 如果在第一种情况下查看i * 100,您会看到它是1730.9999999999998。 强制转换只会删除该点之后的部分(截断)。

Burkhard answered 2020-08-01T12:47:47Z
3 votes

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

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

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

Kevin answered 2020-08-01T12:48:12Z
0 votes

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

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

Gang Su answered 2020-08-01T12:48:37Z
translate from https://stackoverflow.com:/questions/13467849/double-multiplied-by-100-and-then-cast-to-long-is-giving-wrong-value