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


567

다음 프로그램 .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을 사용하고 있습니다.


1
Java 1.7.0에서는 정상적으로
Coffee

2
@ Adel : Oli의 답변에 대한 내 의견을 참조하십시오 .Java 6 은 숫자 를 더한 다음 다음을 사용하여 정밀도를 잃을 수있는 방식 으로이 (및 수행하는 문서)를 구현하는 것처럼 보입니다 . Java 7은 더 이상 그런 식으로 문서화하지 않습니다 (아마도 / 수정했기 때문에). 0.5floor
TJ Crowder

1
내가 작성한 테스트 프로그램의 버그였습니다. ;)
Peter Lawrey

1
부동 소수점 값을 표시하는 다른 예는 액면 값에서 취할 수 없습니다.
Michaël Roy

1
그것에 대해 생각한 후. 문제가 보이지 않습니다. 0.49999999999999994는 0.5보다 작은 가장 작은 표현 가능 숫자보다 크고, 사람이 읽을 수 있는 10 진수 형식 의 표현 자체는 우리를 속이려고 하는 근사치 입니다.
Michaël Roy

답변:


574

요약

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


나는 그 정의가 표시되지 않습니다 round대한 자바 독Math.round 또는의 개요의 Math클래스입니다.
TJ Crowder

3
@ Oli : 아, 이제 흥미 롭습니다. Java 7 (내가 링크 한 문서들)에 대해서는 약간의 시간이 걸렸습니다. 어쩌면 정밀도의 손실을 유발하여 이런 종류의 이상한 행동을 피할 수 있습니다.
TJ Crowder

@TJCrowder : 그렇습니다. 흥미 롭습니다. 개별 Java 버전에 대한 "릴리스 노트"/ "개선"문서가 있는지 알고 있으므로이 가정을 확인할 수 있습니까?
Oliver Charlesworth


1
도움이 될 수는 없지만 제로가 가장 눈에 띄기 때문에이 수정은 외형 일 뿐이라고 생각합니다. 이 반올림 오류의 영향을받는 다른 많은 부동 소수점 값은 의심 할 여지가 없습니다.
Michaël Roy


83

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보다 작은 최대 값이기 때문입니다.


그러면 1.499999999999999994는 어떻게됩니까? 2를 반환합니까? 1을 반환해야하지만 이전과 동일한 오류가 발생하지만 1이 표시됩니다.
mmm

6
1.499999999999999994는 배정 밀도 부동 소수점으로 표현할 수 없습니다. 1.4999999999999998은 1.5보다 작은 최소 2 배입니다. 질문에서 볼 수 floor있듯이이 방법은 올바르게 반올림합니다.
OrangeDog

26

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

Java 7 (테스트에 사용중인 버전)에서 버그가 수정되었습니다.
Iván Pérez

1
나는 당신이 32 비트를 의미한다고 생각합니다. en.wikipedia.org/wiki/ZEBRA_%28computer%29 가 Java를 실행할 수 있을지 의심 되며 그 이후로 33 비트 머신이 있는지 의심됩니다.
chx

@chx : 32 비트 전에 작성했기 때문에 분명히 분명히 :)
Danubian Sailor

11

이후의 답변은 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보다 작습니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.