Development Tip

Math.round (0.49999999999999994)가 1을 반환하는 이유는 무엇입니까?

yourdevel 2020. 10. 3. 12:04
반응형

Math.round (0.49999999999999994)가 1을 반환하는 이유는 무엇입니까?


다음 프로그램 .5에서를 제외한 각 값 이 반 내림 된 것보다 약간 작은 것을 볼 수 있습니다 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);
}

인쇄물

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

Java 6 업데이트 31을 사용하고 있습니다.


요약

자바 6에서 (그리고 아마도 이전) round(x)로 구현됩니다 floor(x+0.5). 1 이것은 정확히이 병리학 적 사례에 대한 사양 버그입니다. 2 Java 7은 더 이상 이러한 잘못된 구현을 요구하지 않습니다.

문제

0.5 + 0.49999999999999994는 배정 밀도에서 정확히 1입니다.

static void print(double d) {
    System.out.printf("%016x\n", Double.doubleToLongBits(d));
}

public static void main(String args[]) {
    double a = 0.5;
    double b = 0.49999999999999994;

    print(a);      // 3fe0000000000000
    print(b);      // 3fdfffffffffffff
    print(a+b);    // 3ff0000000000000
    print(1.0);    // 3ff0000000000000
}

이는 0.49999999999999994의 지수가 0.5보다 작기 때문에 더 해지면 가수가 이동되고 ULP가 커지기 때문입니다.

해결책

Java 7부터 OpenJDK (예 :)가이를 구현합니다. 4

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

1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29

2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (credits to @SimonNickerson for finding this)

3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29

4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29


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.


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) {
        // a is not the 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 the 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 the 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.


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

And, when using round or inverting matrices, etc., these 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

The answer hereafter is an excerpt of an Oracle bug report 6430675 at. Visit the report for the full explanation.

The methods {Math, StrictMath.round are operationally defined as

(long)Math.floor(a + 0.5d)

for double arguments. While this definition usually works as expected, it gives the surprising result of 1, rather than 0, for 0x1.fffffffffffffp-2 (0.49999999999999994).

The value 0.49999999999999994 is the greatest floating-point value less than 0.5. As a hexadecimal floating-point literal its value is 0x1.fffffffffffffp-2, which is equal to (2 - 2^52) * 2^-2. == (0.5 - 2^54). Therefore, the exact value of the sum

(0.5 - 2^54) + 0.5

is 1 - 2^54. This is halfway between the two adjacent floating-point numbers (1 - 2^53) and 1. In the IEEE 754 arithmetic round to nearest even rounding mode used by Java, when a floating-point results is inexact, the closer of the two representable floating-point values which bracket the exact result must be returned; if both values are equally close, the one which its last bit zero is returned. In this case the correct return value from the add is 1, not the greatest value less than 1.

While the method is operating as defined, the behavior on this input is very surprising; the specification could be amended to something more like "Round to the closest long, rounding ties up," which would allow the behavior on this input to be changed.

참고URL : https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1

반응형