최근 DirectX를 사용한 간단한 게임을 TDD를 사용해서 작성하고 있다. 그런데 테스트 도중 D3DXVECTOR3 비교가 자꾸 실패하는 것이다. 스프라이트에 속력과 방향을 지정하고 1초가 흐르게 한 뒤에 정확히 목적지에 도달했는지 테스트하는 코드였다. 3차원 벡터를 사용하기 때문에 1초 후의 좌표는 부동소수로 나오게 된다. 그걸 직접 sqrt() 함수 등으로 계산해서 결과가 같을 것이라고 예상했는데 자꾸 다르게 나오는 것이다.

그래서 브레이크 포인트를 걸고 직접 양쪽 vector를 비교해 보았는데, 소수점 7째 자리부터 약간 달랐다. 당연히 float는 오차가 있지만, 문제는 내가 보았던 아래의 내용이다.

DirectX9를 이용한 3D GAME 프로그래밍 입문(정보문화사) 책에는 아래와 같은 내용이 나온다:

const float EPSILON = 0.001f;
bool Equals(float lhs, float rhs)
{
    // 만약 lhs == rhs라면 두 수의 차이는 0이어야 한다.
    return fabs(lhs - rhs) < EPSILON ? true : false;
}
D3DXVECTOR3 클래스를 이용할 때는 오버로드된 비교 연산자가 이 작업을 대신하므로 우리가 직접 신경 쓸 필요는 없지만, 부동 소수점을 비교하는 방법에 대해서는 반드시 기억하고 있어야 한다.

그래서 이 말대로 당연히 D3DXVECTOR3 클래스를 비교할 때는 오차를 감안해서 비교할 것이라고 생각했는데, 실제로는 그렇지 않았다.

예를 들면 아래와 같은 코드는 비교에 실패한다:

D3DXVECTOR3 torr1(0.0000001f, 0.0f, 0.0f);
D3DXVECTOR3 torr2(0.0000002f, 0.0f, 0.0f);
EXPECT_EQ(torr1, torr2);    // 여기서 실패!

뭔가 문제가 있는 듯 하여 브레이크 포인트를 걸고 비교문을 추적해 보았다.
그 결과 operator==()이 d3dx9math.inl 파일에 아래와 같이 구현되어 있었다:

D3DXINLINE BOOL
D3DXVECTOR3::operator == ( CONST D3DXVECTOR3& v ) const
{
    return x == v.x && y == v.y && z == v.z;
}
헐~ 그냥 == 으로 비교하고 있었다(참고로 x, y, z는 float 형이다). 이래서 오차 범위를 허용하고 있지 않았나보다. 

아무튼 책과 실제 동작이 다르니 한참을 헤메었고, 책을 쓴 사람이 제대로 테스트해본건지 의구심이 들 정도다. 아마도 DirectX가 버전업이 되면서 비교시에 오차를 허용하던 부분이 사라진 게 아닐까 하는 생각만 들 뿐이지만 확인하기가 어렵다.

사실 D3DXVECTOR3와 같은 기본형에 가까운 구조체는 자주 쓰이는 만큼 쓸데없는 연산을 최대한 줄여야 하는 것이 맞다. 심지어 생성자에서도 x, y, z를 0으로 초기화해주지 않는다(이것도 해주는 줄 알고 한번 테스트에 실패했었다).

D3DXVECTOR3 등으로 구글링을 해 보았는데 이와 같은 비교 문제는 보이는게 거의 없다. 다들 어떻게 쓰고 있는건지...??

그래서 D3DXVECTOR3를 랩핑한 래퍼 클래스를 하나 만들어 쓰려고 생각중이다. 여기서 operator==을 하나 정의해서 오차를 허용하도록 써야겠다.

일단은 임시방편으로 아래와 같이 쓰고 있다 -_-;

EXPECT_FLOAT_EQ(expected.x, sprite_.GetPosition().x);
EXPECT_FLOAT_EQ(expected.y, sprite_.GetPosition().y);
EXPECT_FLOAT_EQ(expected.z, sprite_.GetPosition().z);

참고로 테스팅 프레임워크는 GoogleTest를 사용 중이다.

TDD의 좋은 점 중 하나는 이런 식으로 잠재된 문제를 최대한 빨리 수면 위로 부상시켜서, 설계를 이른 시점에 수정할 수 있게 해주는 점 같다.

만약 이걸 당연히 되겠거니 하고 지나갔으면 아마 이상하게 조금씩 나는 오차에 고심하다가 겨우 원인을 찾아내서 D3DXVECTOR3를 사용했던 모든 부분을 뜯어 고치고 있었겠지...


반응형

+ Recent posts