Tuesday, May 29, 2012

Why does Math.round(0.49999999999999994) return 1


In the following program you can see that for each value slightly less that .5 is rounded down, except for 0.5.




for (int i = 10; i >= 0; i--) {
long l = Double.doubleToLongBits(i + 0.5);
double x;
do {
x = Double.longBitsToDouble(l);
System.out.println(x + " rounded is " + Math.round(x));
l--;
} while (Math.round(x) > i);
}



prints




10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 1
0.4999999999999999 rounded is 0



I am using Java 6 update 31.


Source: Tips4all

4 comments:

  1. According to the Java 6 docs, round(x) is implemented as floor(x+0.5).1 But 0.5+0.49999999999999994 is exactly 1 in double precision:

    public static void main(String args[]) {
    double a = 0.5;
    double b = 0.49999999999999994;
    System.out.printf("%016x\n", Double.doubleToLongBits(a));
    System.out.printf("%016x\n", Double.doubleToLongBits(b));
    System.out.printf("%016x\n", Double.doubleToLongBits(a+b));
    System.out.printf("%016x\n", Double.doubleToLongBits(1.0));
    }


    gives:

    3fe0000000000000
    3fdfffffffffffff
    3ff0000000000000
    3ff0000000000000


    This is because 0.49999999999999994 has a smaller exponent than 0.5, so when they're added, its mantissa is shifted, and the ULP gets bigger.




    1. At least, this is the definition given in the Java 6 docs. It's not given in the Java 7 docs, which may explain why people are seeing different behaviour when they run in Java 7. UPDATE: According to Simon Nickerson's answer, it's a known bug, so this almost certainly explains the difference in the docs and the observed behaviour between versions.

    ReplyDelete
  2. This appears to be a known bug (Java bug 6430675: Math.round has surprising behavior for 0x1.fffffffffffffp-2) which has been fixed in Java 7.

    ReplyDelete
  3. source code in JDK 6

    public static long round(double a) {
    return (long)Math.floor(a + 0.5d);
    }


    source code in JDK 7

    public static long round(double a){
    if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5
    return (long)Math.floor(a + 0.5d);
    else
    return 0;


    When the value is 0.49999999999999994d, in JDK 6, it will call floor and hence returns 1 but in JDK 7, the if condition is checking whether the number is greatest double value less than 0.5 or not. As in this case the number is not the greatest double value less than 0.5, so else block returns 0.

    You can try 0.49999999999999999d, which will return 1 but not 0, because this is the greatest double value less than 0.5.

    ReplyDelete
  4. I've got the same on jdk 1.6 32bit, but on java 7 64bit I've go 0 for 0.49999999999999994 rounded is 0 and the last line is not printed. It seems to be VM issue, however, using floating points, you should expect the results to differ a bit on various environments (CPU, 33- or 64-bit mode)

    And, when using round or inverting matrixes or etc., this bits can make a huge difference.

    x64 output:

    10.5 rounded is 11
    10.499999999999998 rounded is 10
    9.5 rounded is 10
    9.499999999999998 rounded is 9
    8.5 rounded is 9
    8.499999999999998 rounded is 8
    7.5 rounded is 8
    7.499999999999999 rounded is 7
    6.5 rounded is 7
    6.499999999999999 rounded is 6
    5.5 rounded is 6
    5.499999999999999 rounded is 5
    4.5 rounded is 5
    4.499999999999999 rounded is 4
    3.5 rounded is 4
    3.4999999999999996 rounded is 3
    2.5 rounded is 3
    2.4999999999999996 rounded is 2
    1.5 rounded is 2
    1.4999999999999998 rounded is 1
    0.5 rounded is 1
    0.49999999999999994 rounded is 0

    ReplyDelete