문제

프로젝트를 디버깅하고 버그를 찾을 수 없었습니다. 마침내 나는 그것을 찾았다. 코드를보십시오. 당신은 모든 것이 정상이라고 생각하고 결과는 "좋아요! OK! OK!", 그렇지 않습니까? 이제 VC로 컴파일합니다 (VS2005 및 VS2008을 시도했습니다).

#include <math.h>
#include <stdio.h>


int main () {
    for ( double x = 90100.0; x<90120.0; x+=1 )
    {
        if ( cos(x) == cos(x) )
            printf ("x==%f  OK!\n", x);
        else
            printf ("x==%f  FAIL!\n", x);
    }

    getchar();
    return 0; 
}

Magic Double Constant는 90112.0입니다. x <90112.0이면 모든 것이 정상입니다. x> 90112.0 -Nope! 당신은 cos를 죄로 바꿀 수 있습니다.

어떤 아이디어? 죄와 cos가 주기적이라는 것을 잊지 마십시오.

도움이 되었습니까?

해결책

이것일 수 있습니다 : http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18

나는 그것이 받아들이 기가 어렵다는 것을 알고 있지만, 부동 소수점 산술은 대부분의 사람들이 기대하는 것처럼 효과가 없습니다. 더 나쁜 것은, 차이점 중 일부는 특정 컴퓨터의 부동 소수점 하드웨어의 세부 사항 및/또는 특정 컴파일러에서 사용하는 최적화 설정에 따라 다릅니다. 당신은 그것을 좋아하지 않을 수도 있지만 그것은 그 방식입니다. "그것을 얻는"유일한 방법은 사물에 대한 가정을 제쳐 놓는 것입니다. 실제로 행동하고 받아들이는 것 하다 행동하다...

( "자주"라는 단어에 중점을두고 동작은 하드웨어, 컴파일러 등에 따라 다릅니다) : 부동 소수점 계산 및 비교는 종종 특수 레지스터를 포함하는 특수 하드웨어에 의해 수행되며 해당 레지스터는 종종 double. 즉, 중간 부동 소수점 계산은 종종 더 많은 비트가 있음을 의미합니다. sizeof(double), 플로팅 포인트 값이 RAM에 기록되면 종종 잘린 상태로, 종종 정밀도를 잃어 버립니다 ...

플로팅 포인트 비교는 까다 롭고 미묘하며 위험에 처해 있습니다. 조심하세요. 떠 다니는 지점 실제로 작품은 대부분의 프로그래머가 생각하는 방식과 다릅니다. 일하다. 플로팅 포인트를 사용하려는 경우 실제로 작동하는 방식을 배워야합니다 ...

다른 팁

다른 사람들이 지적했듯이 VS Math 라이브러리는 X87 FPU에서 계산을 수행하고 있으며 유형이 두 배이지만 80 비트 결과를 생성합니다.

따라서:

  1. cos ()가 호출되고 x87 스택 상단에 80 비트 플로트로 cos (x)로 반환됩니다.
  2. COS (x)는 x87 스택에서 튀어 나와 메모리에 이중으로 저장됩니다. 이로 인해 64 비트 플로트로 반올림됩니다. 가치를 변경합니다
  3. cos ()가 호출되고 x87 스택 상단에 80 비트 플로트로 cos (x)로 반환됩니다.
  4. 둥근 값은 메모리에서 x87 스택에로드됩니다.
  5. cos (x)의 둥글고 어울리지 않은 값은 불평등을 비교합니다.

많은 수학 라이브러리와 컴파일러는 가능한 경우 SSE 레지스터에서 64 비트 플로트에서 계산을 수행하거나 비교 전에 값을 저장 및 반올림하여 실제 계산에 최종 결과를 저장하고 다시로드하여 이로부터 귀하를 보호합니다. cos ()의. 작업중인 컴파일러/라이브러리 조합은 그렇게 용서하지 않습니다.

릴리스 모드에서 생성 된 cos (x) == cos (x) 절차 :

00DB101A  call        _CIcos (0DB1870h) 
00DB101F  fld         st(0) 
00DB1021  fucompp 

값은 한 번 계산 된 다음 복제 된 다음 자체와 비교하여 클로닝됩니다. 결과는 괜찮습니다.

디버그 모드에서도 마찬가지입니다.

00A51405  sub         esp,8 
00A51408  fld         qword ptr [x] 
00A5140B  fstp        qword ptr [esp] 
00A5140E  call        @ILT+270(_cos) (0A51113h) 
00A51413  fld         qword ptr [x] 
00A51416  fstp        qword ptr [esp] 
00A51419  fstp        qword ptr [ebp-0D8h] 
00A5141F  call        @ILT+270(_cos) (0A51113h) 
00A51424  add         esp,8 
00A51427  fld         qword ptr [ebp-0D8h] 
00A5142D  fucompp          

이제 이상한 일이 일어납니다.
1. x는 fstack에로드됩니다 (x, 0)
2. X는 정상 스택에 저장됩니다 (잘린)
3. 코사인이 계산되어 플로트 스택이 발생합니다
4. X가 다시로드됩니다
5. X는 정상 스택에 저장됩니다 (자르기, 현재와 같이 우리는 "대칭 적"입니다)
6. 스택에 있던 1st Cosine의 결과는 메모리에 저장됩니다. 이제 1 번째 값에 대해 다른 잘림이 발생합니다.
7. 코사인이 계산되고 플로트 스택에있는 경우 두 번째 결과가 있지만이 값은 한 번만 잘 렸습니다.
8. 첫 번째 값은 FSTACK에로드되었지만이 값은 두 번 잘라 냈습니다 (코사인을 계산하기 전에 한 번, 한 번).
9.이 두 값은 비교됩니다. 반올림 오류가 발생합니다.

당신은해야합니다 절대 대부분의 경우 평등에 대한 복식을 비교하지 마십시오. 당신은 당신이 기대하는 것을 얻지 못할 수도 있습니다.

플로팅 포인트 레지스터는 메모리 값과 다른 크기를 가질 수 있습니다 (현재 인텔 머신에서 FPU 레지스터는 80 비트 대 64 비트 복식). 컴파일러가 첫 번째 코사인을 계산하는 코드를 생성하고 값을 메모리에 저장하고 두 번째 코사인을 계산하며 레지스터의 메모리 값을 비교하면 값이 다를 수 있습니다 (반올림 문제로 인해 80 ~ 64 비트). .

부동 소수점 값은 약간 까다 롭습니다. 플로팅 포인트 비교를위한 Google.

컴파일러는 64 비트 이중 값을 80 비트 내부 부동 소수점 레지스터와 비교하는 코드를 생성했을 수 있습니다. 평등에 대한 부동 소수점 값을 테스트하는 것은 이러한 종류의 오류가 발생하기 쉽습니다. (VAL1 == VAL2)가 아닌 "FABS (VAL1 -VAL2) <Epsilon)과 같은"퍼지 "비교를하는 것이 거의 항상 더 좋습니다.

루프 제어 변수로 플로트 값을 증가시키고 테스트하는 것은 일반적으로 정말 나쁜 생각입니다. 필요한 경우 루핑을 위해 별도의 int LCV를 만듭니다.

이 경우 더 쉽습니다.

for ( int i = 90100; i<90120; i+=1 )    {
    if ( cos(i) == cos(i) )
        printf ("i==%d  OK!\n", i);
    else
        printf ("i==%d  FAIL!\n", i);
}

문제 주위에 어떻게해야합니까? 수정하다 만약에 차단하다:

if ( (float)cos(x) == (float)cos(x) )
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top