双倍乘以100,然后投给长是给错误的价值

我有以下代码:

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

O / P: 1730 //Expected:1731

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

O / P: 1732 //Expected:1733

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

O / P: 1732 //Expected:1732{As expected}

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

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

我曾试图Google,但无法find原因。如果问题微不足道,我很抱歉。

似乎没有一个答案是为了解释 17.32 为什么不同。

1.为什么发生

17.3217.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.内部表述(certificate):

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乘法( 参考 )步骤的结果自己对向最近的舍入。 这改变了结果如预期的那样,但是其原因仍然与上述完全相同。

最后,由于演员(long) ,截断发生,并留下你看到的结果。 (1730, 1732, 1732)

缩小基元转换: Java™语言规范 §5.1.3

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

double值不是17.31,而是17.309999999999999。 这就是为什么当你乘以100你得到1730.99999999999999999。 在转换为Long之后,你的double值被截断为零。 所以你得到1730。

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

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

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

这将允许程序弥补使用浮点计算inheritance的非常小的错误,通过不使用floor操作,如默认(长)所做的那样。

这与内部表示有关。 如果你看第一个例子中的i * 100,你会发现它是1730.9999999999998。 点击后只会删除零件(截断)。

克苏鲁和svz的答案是正确的。 如果要将double乘以100并避免浮点舍入错误,则可以使用Math.round()在每次乘法之后将结果舍入为最接近的long整数:

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

处理极大(或负)双精度时,这仍然会有浮点错误。 双倍的绝对值越大,它与Java可以表示的下一个双倍之间的差异就越大。 在某一点之后,连续双打大于一个整数,常规舍入将无法平滑差异。 对于你张贴的例子,这应该工作,虽然。

当你进行这种长时间的转换时, 你的17.31实际上可能是17.30999999999,这就是为什么1730年而不是1731年。

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