0.1을 여러 번 더해도 손실이없는 이유는 무엇입니까?


152

나는 0.1십진수가 유한 이진수 ( 설명 )로 정확하게 표현 될 수 없다는 것을 알고 있으므로 double n = 0.1약간의 정밀도를 잃고 정확하게되지 않습니다 0.1. 반면에는 0.5이므로 정확하게 표현할 수 있습니다 0.5 = 1/2 = 0.1b.

0.1 세 번 추가 하면 정확히 0.3다음 코드가 인쇄 되지는 않는다고 이해할 수 있습니다 false.

double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
    sum += d;
System.out.println(sum == 0.3); // Prints false, OK

그러나 0.1 5 번 을 추가 하면 정확히 0.5어떻게됩니까? 다음 코드가 인쇄됩니다 true.

double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
    sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?

0.1정확하게 표현할 수 없다면 어떻게 5 번 더하면 정확하게 0.5표현할 수 있습니까?


7
당신이 정말로 그것을 연구한다면 당신이 그것을 알아낼 수 있다고 확신하지만, 부동 소수점에는 "놀람"이로 드되고 때로는 경이로움을 보는 것이 좋습니다.
핫 릭

3
당신은 이것에 대해 수학적으로 생각하고 있습니다. 부동 소수점 대수는 어떤 식 으로든 수학이 아닙니다.
Jakob

13
@HotLicks는 매우 잘못된 태도입니다.
홉스

2
@RussellBorogove는 최적화 된 sum경우에도 루프가 실제로 실행 된 것과 동일한 최종 값을 가진 경우에만 유효한 최적화 입니다. C ++ 표준에서는이를 "있는 그대로"또는 "동일한 관찰 가능한 동작"이라고합니다.
홉스

7
@ 야콥은 전혀 사실이 아닙니다. 부동 소수점 산술은 오류 범위 등의 우수한 수학적 처리로 엄격하게 정의됩니다. 많은 프로그래머들이 분석에 대해 기꺼이 따르지 않거나 단지 "부동 소수점이 정확하지 않다"는 것만으로도 분석이 귀찮은 가치가 없다고 잘못 믿는 것입니다.
홉스

답변:


155

반올림 오류는 무작위가 아니며 구현 방식은 오류를 최소화하려고 시도합니다. 이는 때때로 오류가 표시되지 않거나 오류가 없음을 의미합니다.

예를 들어 0.1정확히되지 0.1new BigDecimal("0.1") < new BigDecimal(0.1)하지만 0.5정확히1.0/2

이 프로그램은 실제 가치를 보여줍니다.

BigDecimal _0_1 = new BigDecimal(0.1);
BigDecimal x = _0_1;
for(int i = 1; i <= 10; i ++) {
    System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue());
    x = x.add(_0_1);
}

인쇄물

0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0

참고 : 그것은 0.3약간 꺼져 있지만 0.4비트에 도달하면 53 비트 제한에 맞도록 비트를 아래로 이동해야하며 오류는 무시됩니다. 또, 에러에 대한 크립 위로 0.6하고 0.7있지만 위해 0.81.0오류가 폐기된다.

5 번 추가하면 오류가 누적되고 취소되지 않습니다.

오류가 발생하는 이유는 정밀도가 제한되어 있기 때문입니다. 즉, 53 비트. 즉, 숫자가 커질수록 더 많은 비트를 사용하므로 비트를 끝까지 버려야합니다. 이 경우 반올림이 발생하여 선호합니다.
더 작은 숫자 (예 : 0.1-0.0999=>)를 얻을 때 반대 효과를 얻을 수 있으며 1.0000000000000286E-4 이전보다 더 많은 오류가 표시됩니다.

Java 6에서 Math.round (0.49999999999999994)가 1을 반환하는 이유는 이것의 예입니다 .이 경우 계산에서 비트 손실이 답변과 큰 차이를 가져옵니다.


1
이것은 어디에 구현됩니까?
EpicPandaForce

16
@Zhuinden CPU는 IEEE-754 표준을 따릅니다. Java는 기본 CPU 명령어에 대한 액세스를 제공하며 관여하지 않습니다. en.wikipedia.org/wiki/IEEE_floating_point
Peter Lawrey

10
@PeterLawrey : 반드시 CPU 일 필요는 없습니다. CPU에 부동 소수점이없고 (사용중인 별도의 FPU가없는) 머신에서 IEEE 산술이 소프트웨어에 의해 수행됩니다. 호스트 CPU에 부동 소수점이 있지만 IEEE 요구 사항을 준수하지 않는 경우 해당 CPU에 대한 Java 구현도 소프트 부동 부동 소수점을 사용해야한다고 생각합니다.
R .. GitHub STOP HELPING ICE

1
@R ..이 경우 strictfp Time을 사용 하여 고정 소수점 정수를 고려 하면 어떻게 될지 모르겠습니다 . (또는 BigDecimal)
Peter Lawrey

2
@eugene 핵심 문제는 부동 소수점이 나타낼 수있는 제한된 값입니다. 이 제한으로 인해 정보가 손실되고 숫자가 커지면 오류가 발생할 수 있습니다. 반올림을 사용하지만이 경우 반올림되므로 0.1이 약간 너무 커서 약간 큰 숫자가 올바른 값으로 바뀝니다. 정확히 0.5
Peter Lawrey

47

부동 소수점에서의 오버플로 금지 x + x + x는 실제 3 *로 정확하게 올림 된 (즉 가장 가까운) 부동 소수점 숫자 x이며, x + x + x + x정확히 4 * x이며, x + x + x + x + x5 *의 경우 올바르게 반올림 된 부동 소수점 근사값입니다 x.

에 대한 첫 번째 결과 는 정확한 x + x + x사실에서 비롯됩니다 x + x. x + x + x따라서 단 하나의 반올림 결과입니다.

두 번째 결과는 더 어렵습니다. 여기 에서 한 가지 시연이 설명 됩니다 (스티븐 캐논은의 마지막 세 자리에 대한 사례 분석을 통해 다른 증거를 제시합니다 x). 요약하면, 3 * x는 2 * 와 동일한 2 진법에x 있거나 4 *와 같은 2 진법에 있으며 x, 각각의 경우 세 번째 덧셈의 오류가 두 번째 덧셈의 오류를 취소한다고 추론 할 수 있습니다 ( 우리가 이미 말했듯이 첫 번째 추가는 정확합니다.)

세 번째 결과 인 " x + x + x + x + x올바르게 반올림 됨 " 은 첫 번째 결과 의 정확도와 동일한 방식으로 두 번째 결과에서 파생됩니다 x + x.


두 번째 결과는 왜 0.1 + 0.1 + 0.1 + 0.1정확히 부동 소수점 숫자인지를 설명합니다 0.4. 유리수 1/10과 4/10은 부동 소수점으로 변환 될 때 동일한 상대 오차로 동일한 방식으로 근사됩니다. 이 부동 소수점 숫자의 비율은 정확히 4입니다. 상기 제 1 및 제 결과 표시 0.1 + 0.1 + 0.1와는 0.1 + 0.1 + 0.1 + 0.1 + 0.1그 자체로, 그들은 단지 각각에 결과를 관련, 순 에러 분석에 의해 추정되는 것보다 더 적은 에러를 갖는 것으로 예상 될 수 있지만, 3 * 0.15 * 0.1근접하지만 반드시 일치하는 것으로 예상 될 수있는, 0.3그리고 0.5.

0.1네 번째 덧셈 후에 계속 덧셈 을하면, 마지막으로“ 0.1n 번에 자신을 더한 값”이 n에서 n * 0.1분기되고 n / 10에서 더 많이 갈라지는 반올림 오류가 나타납니다 . n의 함수로 "0.1 n을 자신에 추가 한 n"의 값을 플로팅하는 경우, n의 가산 결과가 특정 이진법으로 떨어지 자마자 이진법으로 일정한 기울기 선을 관찰 할 수 있습니다. 첨가물의 특성은 동일한 이진법으로 결과를 생성 한 이전 첨가와 유사 할 것으로 예상된다). 동일한 바이너리 내에서 오류가 커지거나 줄어 듭니다. 이진법에서 이진법으로 기울기 순서를 살펴보면 반복되는 자릿수를 인식 할 수 있습니다.0.1잠시 동안 바이너리로. 그 후 흡수가 시작되고 곡선이 평평 해집니다.


1
첫 번째 줄에서 x + x + x는 정확하지만 질문의 예에서는 그렇지 않습니다.
Alboz

2
@Alboz 나는 x + x + x정확히 3을 실수로 정확하게 반올림 한 부동 소수점 숫자 라고 말합니다 x. "올바른 반올림"은이 문맥에서 "가장 가까운"을 의미합니다.
Pascal Cuoq

4
+1 이것은 정답입니다. 실제로 모호한 일반성보다는 진행중인 일에 대한 설명 / 증거를 제공합니다.
R .. GitHub 중지 지원 얼음

1
@Alboz (모두 질문에 의해 구상 됨). 그러나이 대답은 최악의 경우에 오류를 합치 지 않고 실수로 취소하는 방법입니다.
홉스

1
@chebus 0.1은 0x1.999999999999999999999… p-4 (16 진수 (무한 자릿수))입니다. 배정 밀도로 0x1.99999ap-4로 추정됩니다. 0.2는 0x1.999999999999999999999… p-3 (16 진수)입니다. 0.1이 0x1.99999ap-4로 근사 된 것과 같은 이유로 0.2는 0x1.99999ap-3으로 근사됩니다. 한편, 0x1.99999ap-3도 정확히 0x1.99999ap-4 + 0x1.99999ap-4입니다.
Pascal Cuoq

-1

부동 소수점 시스템은 반올림을위한 몇 가지 추가 정밀도를 포함하여 다양한 마법을 수행합니다. 따라서 0.1의 부정확 한 표현으로 인한 매우 작은 오류는 0.5로 반올림됩니다.

부동 소수점은 숫자를 나타내는 훌륭하지만 부정확 한 방법이라고 생각하십시오. 가능한 모든 숫자가 컴퓨터에서 쉽게 표현되는 것은 아닙니다. PI와 같은 비이성적 인 숫자. 또는 SQRT (2)처럼. (심볼 수학 시스템은 그것들을 나타낼 수 있지만 "쉽게"라고 말했습니다.)

부동 소수점 값은 매우 가깝지만 정확하지 않을 수 있습니다. 명왕성으로 이동하여 밀리미터 단위로 벗어날 수있을 정도로 가까이있을 수 있습니다. 그러나 여전히 수학적 의미에서는 정확하지 않습니다.

대략적인 것이 아니라 정확해야하는 경우 부동 소수점을 사용하지 마십시오. 예를 들어, 회계 응용 프로그램은 계정에서 특정 수의 동전을 정확하게 추적하려고합니다. 정수는 정확하기 때문에 좋습니다. 정수로 감시해야 할 주요 문제는 오버플로입니다.

기본 표현은 정수이지만 큰 표현이지만 통화에 BigDecimal을 사용하면 효과적입니다.

부동 소수점 숫자가 정확하지 않음을 인식하면 여전히 많은 용도로 사용됩니다. 그래픽 시스템의 내비게이션 또는 좌표를위한 좌표 시스템. 천문학적 가치. 과학적 가치. (어쨌든 전자 덩어리 내에서 야구 덩어리의 정확한 질량을 알 수 없으므로 부정확성은 실제로 중요하지 않습니다.)

계산 응용 프로그램 (회계 포함)의 경우 정수를 사용하십시오. 게이트를 통과하는 사람들의 수를 세려면 int 또는 long을 사용하십시오.


2
질문은 [java]로 태그됩니다. 자바 언어 정의는 없습니다 에는 단지 몇 푼 지수 비트의 "정밀의 몇 가지 여분의 비트"에 대한 조항을 (그리고 사용하지 않는 경우에만 즉 strictfp). 당신이 무언가를 이해하기 위해 포기했다고해서 그것이 헤아릴 수 없거나 다른 사람들이 그것을 이해하기 위해 포기해야한다는 것을 의미하지는 않습니다. 언어 정의를 구현하기 위해 Java 구현이 수행 할 길이의 예는 stackoverflow.com/questions/18496560 을 참조하십시오 (여분의 정밀도 비트 또는 strictfp추가 exp 비트에 대한 규정은 포함되지 않음 )
Pascal Cuoq
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.