双倍乘以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.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.内部表述(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()将解决这个问题。