2009년 12월 30일
나누기와 fmod
다음의 예제코드를 한번 보자. 간단히 코드 설명을 하자면...
270도에 해당되는 gap을 22.5도에 해당되는 border로 나눈 다음 나머지는 fmod 로 구한다
라는 내용의 코드에 해당된다.
/////////////////////////////////////////////////////////////////////
#define D3DX_PI ((FLOAT) 3.141592654f) // d3dx9math.h
float gap = D3DX_PI * 1.5; // 4.7123890
float border = D3DX_PI / 8; // 0.39269909
int quot = (int) (gap / border);
float mod = ::fmod(gap, border);
/////////////////////////////////////////////////////////////////////
일반적인 컴파일 환경에서 이 빌드는 다음과 같은 리턴값을 내게 된다.
quot : 11
mod : 0.39269897
사실은 quot 가 12가 나오고 mod는 0이 나오는 것을 예상했지만 float 연산에서의 말로는 표현할 수 없는 정확도와 관련된 작은 오차로 인해서 위와 같은 결과값이 나오는 것이라고 예상 할 수 있겠다.
그럼 컴파일 옵션을 다음과 같이 바꿔 보자
구성속성 -> C/C++ -> 코드 생성 에 들어가서
부동 소수점 모델을 Fast (/fp:fast) 로 변경
고급 명령어 집합 사용을 스트리밍 SIMD Extensions(/arch:SSE) 로 변경
그 이후에 다시 저 값을 돌려보면 다른 연산 결과가 나온다.
quot : 12
mod : 0.39269897
원래 도출하려고 했던 답으로는 아주 곤란하다. quot는 12가 나왔지만 mod는 원래대로의 컴파일 옵션과 다를것 없는 결과가 나와버렸다.
해당 컴파일된 내용을 디어셈블러로 보게 되면 약간 재미있는것을 보게 되는데 소스에서 int quot = (int)(gap / border) 하는 부분인데,
// /fast 옵션과 SIMD, SIMD2의 옵션이 모두 켜졌을때
int quot = (int) (gap / border);
004113F8 movss xmm0,dword ptr [gap]
004113FD divss xmm0,dword ptr [border]
00411402 cvttss2si eax,xmm0
00411406 mov dword ptr [quot],eax
// SMID 또는 /fast 옵션 두개중 한개만 켜있거나 둘다 꺼져있는 경우
int quot = (int) (gap / border);
00411420 fld dword ptr [gap]
00411423 fdiv dword ptr [border]
00411426 call @ILT+230(__ftol2_sse) (4110EBh)
0041142B mov dword ptr [quot],eax
다른건 둘째치고 굵게 처리된 부분에서 약간의 차이가 존재한다.
위의 경우에는 CPU에서 지원하는 cvttss2si 라는 명령어 셋을 호출한다. 저 명령어 셋은 간단히 실수형 체계를 정수형 체계로 전환하는 명령어 셋이라고 볼 수 있는데 지원되는 CPU가 한정된 명령어 셋이라고 볼수 있는 반면, 밑의 경우는 따로 어떠한 함수를 호출하게 된다.
이 결과를 어떻게 받아들일지는 결론은 개개인에 맡기고 싶다.
원래 도출하고 싶은 결론은 SMID와 /fast 옵션을 둘다 킬때에는 저런 실수 나누기 연산에 유의해야 한다는 점이였지만 다른 재미있는 현상도 발견하게 되어 같이 적어봤다.
# by | 2009/12/30 11:36 | Programmable | 트랙백 | 덧글(0)



