双倍乘以100然后转换为长度给出错误的值

xyz*_*xyz 43 java floating-point numbers

我有以下代码:

Double i=17.31;
long j=(long) (i*100);
System.out.println(j);
Run Code Online (Sandbox Code Playgroud)

O/P: 1730 //Expected:1731

Double i=17.33;
long j=(long) (i*100);
System.out.println(j);
Run Code Online (Sandbox Code Playgroud)

O/P: 1732 //Expected:1733

Double i=17.32;
long j=(long) (i*100);
System.out.println(j);
Run Code Online (Sandbox Code Playgroud)

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

Double i=15.33;
long j=(long) (i*100);
System.out.println(j);
Run Code Online (Sandbox Code Playgroud)

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

我曾试图谷歌,但无法找到理由.如果问题很简单,我很抱歉.

Ani*_*han 74

似乎没有任何答案可以解决为什么 17.32采取不同的行动.

1.为什么会这样

在你们之间看行为的差异17.3217.33 & 17.31是由于IEEE-754 舍入规则.

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

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


你的案子:

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
Run Code Online (Sandbox Code Playgroud)

3.内部代表(证明):

17.31 :(尾数比较)

Actual:   1.00010100111101011100001010001111010111000010100011110...
Internal: 1.0001010011110101110000101000111101011100001010001111
Run Code Online (Sandbox Code Playgroud)

17.32 :(尾数比较)

Actual:   1.00010101000111101011100001010001111010111000010100011... 
Internal: 1.0001010100011110101110000101000111101011100001010010    //round-up!
Run Code Online (Sandbox Code Playgroud)

17.33 :(尾数比较)

Actual:   1.00010101010001111010111000010100011110101110000101000...
Internal: 1.0001010101000111101011100001010001111010111000010100
Run Code Online (Sandbox Code Playgroud)

4.转换回小数:

17.31 ->  17.309999999999998721023075631819665431976318359375...
17.32 ->  17.32000000000000028421709430404007434844970703125... //(was rounded up)
17.33 ->  17.3299999999999982946974341757595539093017578125...
Run Code Online (Sandbox Code Playgroud)

(IEEE-754分析工具)

5.投长

编辑:正如@Jeppe Stig Nielsen所说,在你的乘法步骤中有一个更重要的因素.FP乘法(参考)步骤的结果自己舍入到最近.这改变了哪些结果是预期的,哪些不是,但原因仍然与上述完全相同.

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

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

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

  • 赞同这个答案也是因为链接到漂亮的工具.但是:请记住,乘以"100.0"会导致进一步的舍入.考虑案例**`17.34`**.将此数字表示为"Double"_also_必须舍入_down_.但是在乘以"100.0"之后它仍然会给出预期的输出!原因是乘法算法的一部分是舍入去除不能适合结果"Double"的数字.在"17.34*100.0"的情况下,舍入_after_乘以圆_up_,因此我们很幸运得到"预期"结果. (4认同)

svz*_*svz 22

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

  • 这类问题首先是编程世界的问题. (7认同)
  • 确切地说,它表示为17.309999999999998721023075631819665431976318359375. (4认同)
  • 确切地说,它根本不是十进制的,它是二进制的.像17.3125这样的数字具有精确的二进制表示,因为它是17 + 5/16,但是17.31没有精确的二进制表示.你不能把它表达为两个权力的有限和. (3认同)

Kev*_*ica 6

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

Double i=17.31;
long j=Math.round(i*100);
System.out.println(j);
Run Code Online (Sandbox Code Playgroud)

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


Pea*_*oto 5

如已经解释的那样,这是由于非常小的浮点精度.

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

long j=Math.round(i*100);
Run Code Online (Sandbox Code Playgroud)

这将允许程序通过不使用floor操作来补偿使用浮点计算继承的非常小的错误,如默认(long)那样.