2009-08-26 3 views
8

이것은 버그 일뿐입니다.gcc 정밀 버그?

double sum_1 = 4.0 + 6.3; 
assert(sum_1 == 4.0 + 6.3); 

double t1 = 4.0, t2 = 6.3; 

double sum_2 = t1 + t2; 
assert(sum_2 == t1 + t2); 

하지 않으면 버그, 이유 : 두 번째 실패하면서 첫 번째 어설 통과?

+0

다음과 같이 볼 수도 있습니다. - [http://stackoverflow.com/questions/21265/comparing-ieee-floats-and-doubles-for-equality](http://stackoverflow.com/questions/21265/) 비교 ieee-floats-and-doubles-for-equality) - [http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison](http://stackoverflow .com/questions/17333/most-effective-way-for-float-and-double-comparison) - [http://stackoverflow.com/questions/713763/strange-results-with-floating-point-comparison] – pingw33n

답변

12

이것은 나에게도 물린 것입니다.

예, 반올림 오류로 인해 부동 소수점 수를 비교해서는 안되며, 아마 알고있을 것입니다.

하지만이 경우에는 t1+t2을 계산하고 다시 계산합니다. 확실하게 동일한 결과를 산출해야하는?

여기에 설명되어 있습니다. 이걸 x86 CPU에서 돌리고있을거야, 맞습니까? x86 FPU는 내부 레지스터에 80 비트를 사용하지만 메모리의 값은 64 비트 2 배로 저장됩니다.

따라서 t1+t2은 먼저 정밀도가 80 비트로 계산 된 다음 정밀도가 64 비트 인 sum_2의 메모리에 저장되고 일부 반올림이 발생합니다. 어설 션을 위해 부동 소수점 레지스터에 다시로드되고 t1+t2이 80 비트 정밀도로 다시 계산됩니다. 이제 이전에 64 비트 부동 소수점 값으로 반올림 한 sum_2t1+t2으로 더 높은 정밀도 (80 비트)로 계산 했으므로 값이 정확히 일치하지 않습니다.

왜 첫 번째 테스트는 통과합니까? 이 경우 컴파일러는 컴파일시에 4.0+6.3을 평가하고이를 할당 및 어설 션 모두에 대해 64 비트 수량으로 저장합니다.그래서 동일한 값이 비교되고 assert가 성공합니다.

두 번째 편집 다음은 주석이있는 코드 (GCC, 86)의 두 번째 부분에 대해 생성 된 어셈블리 코드의 - 거의가 위에서 설명한 시나리오 다음과

// t1 = 4.0 
fldl LC3 
fstpl -16(%ebp) 

// t2 = 6.3 
fldl LC4 
fstpl -24(%ebp) 

// sum_2 = t1+t2 
fldl -16(%ebp) 
faddl -24(%ebp) 
fstpl -32(%ebp) 

// Compute t1+t2 again 
fldl -16(%ebp) 
faddl -24(%ebp) 

// Load sum_2 from memory and compare 
fldl -32(%ebp) 
fxch %st(1) 
fucompp 

재미있는 측면 참고 :이 최적화없이 컴파일되었습니다. -O3으로 컴파일 될 때 컴파일러는 코드 모두 을 최적화합니다.

+0

나는 그것이 심지어 그것이라고 생각하지 않는다. 것은'4.0 + 6.3'은 컴파일러에 의해 10.3으로 상수 형으로 접혀진 표현식입니다. 따라서 첫 번째 주장은'assert (10.3 == 10.3)'과 동등해진다. 두 번째 테스트에서는 실제로는 4.0을, 두 번에 넣고, 6.3을 사용하고, 두 배로 (작은 정밀도를 잃어 버림) 두 개를 합치고, 더한 값을 * 상수 10.3과 비교합니다. 2^-70 정도 다르다. :) – hobbs

+0

컴파일러가 assert 문에서 t1 + t2를 10.3으로 최적화 할 수 있다면 왜 sum_2에 대한 할당에서 동일한 최적화를 수행 할 수 없습니까? –

+0

목록을 보면 나에게있어. :) 내 부분에 가난한 추측, 나는 생각한다. – hobbs

13

부동 소수점 숫자를 비교하고 있습니다. 그렇게하지 않으면, 부동 소수점 숫자는 경우에 따라 고유 한 정밀도 오류가 있습니다. 대신에 두 값의 차이의 절대 값을 취하여 값이 작은 수 (ε)보다 작다고 주장하십시오.

void CompareFloats(double d1, double d2, double epsilon) 
{ 
    assert(abs(d1 - d2) < epsilon); 
} 

이것은 컴파일러와 관련이 없으며 부동 소수점 숫자가 구현되는 방식과 관련이 있습니다. 여기에 IEEE 사양은 다음과 같습니다 이중 정밀도 숫자의

http://www.eecs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF

+4

에드 (Ed)가 말하는 정밀도 오류는 부동 소수점 연산의 기능입니다. 버그는 아니며 gcc 작성자의 부주의 한 구현이 아니라 컴퓨터가 분수로 산술을 수행하는 방식입니다. Google은 '모든 컴퓨터 과학자가 부동 소수점 연산에 대해 알아야하는 것'에 대해 중점적으로 강의하고 있습니다. –

+0

예, 사양에 대한 링크를 추가했습니다. –

+1

Google에도이 "버그"가 있습니다. http://www.google.com/search?hl=ko&source=hp&q=99999999999999++-999999999999998&btnG=Google+Search&cts=1251328513752&aq=f&oq=&aqi= – llamaoo7

1

비교는 본질적으로 정확하지 않습니다. 예를 들어 0.0 == 0.0이 을 반환하는 경우가 있습니다. 이는 FPU가 숫자를 저장하고 추적하는 방식 때문입니다.

Wikipedia says : 평등에 대한

테스트는 문제가있다. 수학적으로 동일한 두 계산 열은 서로 다른 부동 소수점 값을 생성 할 수 있습니다.

델타를 사용하여 정확한 값이 아닌 비교를 허용해야합니다.

2

두 경우 중 하나에서 64 비트 더블을 80 비트 내부 레지스터와 비교할 수 있습니다. GCC에서 두 가지 경우에 대해 내놓는 어셈블리 지침을 살펴 보는 것이 계몽적일 수 있습니다 ...

3

내 인텔 코어 2 듀오에서 문제가 중복되어 어셈블리 코드를 살펴 보았습니다. 여기에 무슨 일이 일어나고 있는지의 : 컴파일러가 t1 + t2을 평가할 때,

round the 80-bit sum to a 64-bit number and store it 

는 그 다음 == 비교 64 비트 합계 80 비트의 합을 비교하지

load t1 into an 80-bit register 
load t2 into an 80-bit register 
compute the 80-bit sum 

가 저장 sum_2으로 수행, 왜냐하면 주로 부분적인 부분 0.3은 2 진 부동 소수점 숫자를 사용하여 정확하게 표현 될 수 없기 때문에 두 개의 다른 길이로 잘린 '반복 십진수'(실제로 반복 바이너리)를 비교합니다.

gcc -O1 또는 gcc -O2으로 컴파일하면 gcc가 컴파일 할 때 잘못된 산술을 수행하고 문제가 사라집니다. 어쩌면 이것은 표준에 따르면 괜찮지 만 gcc가 내가 가장 좋아하는 컴파일러가 아닌 이유 중 하나 일뿐입니다.


P. ==은 80 비트 합계와 64 비트 합계를 비교한다고 말하면 물론 실제로 64 비트 합계의 확장 버전을 비교한다는 의미입니다. 당신은 부동 소수점의 놀라운 세계에

sum_2 = round(extend(t1) + extend(t2)) 

시작으로

extend(sum_2) == extend(t1) + extend(t2) 

sum_2 = t1 + t2 

가 해결에

sum_2 == t1 + t2 

가 해결 생각을 잘 할 수있다!

3

당신이 일반적으로

예를 들어
if (abs(x) != 0 || abs(y) != 0) 
    rel_diff (x, y) = abs((x - y)/max(abs(x),abs(y)) 
else 
    rel_diff(x,y) = max(abs(x),abs(y)) 

으로 정의된다 상대적인 차이를 측정 할 친밀감에 대한 부동 소수점 숫자를 비교, 아이디어는 선도의 수를 측정하는 것입니다

rel_diff(1.12345, 1.12367) = 0.000195787019 
rel_diff(112345.0, 112367.0) = 0.000195787019 
rel_diff(112345E100, 112367E100) = 0.000195787019 

숫자가 공통으로 가지고있는 유효 숫자; 0.000195787019의 -log10을 가져 가면 3.70821611이되는데, 이것은 모든 예제가 공통으로 가지고있는 기본 10 자리 숫자에 해당합니다.

두 개의 부동 소수점 숫자는 기계 엡실론 사용되는 부동 소수점 하드웨어의 가수에서 개최 할 수있는 가장 작은 수입니다 당신이

if (rel_diff(x,y) < error_factor * machine_epsilon()) then 
    print "equal\n"; 

처럼 뭔가해야 동일한 경우 결정해야합니다. 대부분의 컴퓨터 언어에는이 값을 얻기위한 함수 호출이 있습니다. error_factor는 숫자 x와 y의 계산에서 반올림 오류 (및 기타)에 의해 소비 될 것으로 생각되는 유효 자릿수에 기초해야합니다. 예를 들어, x와 y가 약 1000 개의 합계의 결과이고 합계되는 숫자의 경계를 알지 못하는 경우 error_factor를 약 100으로 설정합니다.

링크로 추가하려고 시도했지만 ' 이 이후 t 내 첫 번째 게시물입니다 :

  • en.wikipedia.org/wiki/Relative_difference
  • en.wikipedia.org/wiki/Machine_epsilon
  • en.wikipedia.org/wiki/Significand (가수)
  • ko.wikipedia.org/wiki/Rounding_error