IT story

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

hot-time 2020. 2. 9. 19:23
반응형

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을 사용하고 있습니다.


요약

Java 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가 커지기 때문입니다.

해결책

(예) 자바 7 오픈 JDK 따라서 구현을 보낸 사람 : 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 (이를 찾기 위해 @SimonNickerson에 신용을 제공)

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


이것은 알려진 버그 ( Java 버그 6430675 : Math.round에 0x1.fffffffffffffp-2의 놀라운 동작이 있음 ) 인 것으로 보이며 Java 7에서 수정되었습니다.


JDK 6의 소스 코드 :

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

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;
    }
}

값이 0.49999999999999994d 인 경우 JDK 6에서는 floor 를 호출 하여 1을 리턴하지만 JDK 7에서는 if숫자가 0.5보다 작은 최대 이중 값인지 여부를 확인합니다. 이 경우 숫자는 0.5보다 작은 최대 값이 아니므로 else블록은 0을 반환합니다.

0.49999999999999999d를 시도하면 1을 반환하지만 0은 반환하지 않습니다. 이는 0.5보다 작은 최대 값이기 때문입니다.


JDK 1.6 32 비트에서도 동일하지만 Java 7 64 비트에서는 0.49999999999999994에 대해 0을 얻었으며 반올림은 0이며 마지막 줄은 인쇄되지 않습니다. VM 문제인 것처럼 보이지만 부동 소수점을 사용하면 다양한 환경 (CPU, 32 비트 또는 64 비트 모드)에서 결과가 약간 다를 것으로 예상해야합니다.

그리고 round행렬 등을 사용 하거나 반전 시킬 때 이러한 비트 는 큰 차이를 만들 수 있습니다.

x64 출력 :

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

이후의 답변은 Oracle 버그 보고서 6430675 에서 발췌 한 것입니다 . 전체 설명을 보려면 보고서를 방문하십시오.

{Math, StrictMath.round 메소드는 운영상 다음과 같이 정의됩니다.

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

이중 인수. 이 정의는 일반적으로 예상대로 작동하지만 0x1.fffffffffffffp-2 (0.49999999999999994)에 대해 0이 아니라 1의 놀라운 결과를 제공합니다.

값 0.49999999999999994는 0.5보다 작은 가장 큰 부동 소수점 값입니다. 16 진 부동 소수점 리터럴의 값은 0x1.fffffffffffffp-2이며 (2-2 ^ 52) * 2 ^ -2와 같습니다. == (0.5-2 ^ 54). 따라서 합계의 정확한 값

(0.5 - 2^54) + 0.5

1-2 ^ 54입니다. 이것은 인접한 두 부동 소수점 숫자 (1-2 ^ 53)와 1 사이의 중간입니다. IEEE 754 산술 둥근에서 Java가 사용하는 가장 가까운 짝수 반올림 모드로, 부동 소수점 결과가 정확하지 않을 때 정확한 결과를 괄호로 나타내는 표현 가능한 부동 소수점 값; 두 값이 모두 비슷하면 마지막 비트 0이 반환됩니다. 이 경우 더하기의 올바른 반환 값은 1이며 가장 큰 값은 1보다 작습니다.

메소드가 정의 된대로 작동하는 동안이 입력의 동작은 매우 놀랍습니다. 이 사양은 "가장 긴 반올림, 반올림으로 반올림"과 같은 방식으로 수정 될 수 있으며이 입력의 동작을 변경할 수 있습니다.

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



반응형