김종하 파수닷컴 선임

▲ 김종하 파수닷컴 선임 [PA사업팀 EA팀] http://story.wisedog.net/

[아이티데일리] 여러 코딩 표준(JSF, MISRA 등)에서는 반복문 카운터로 부동 소수점 사용을 금하고 있습니다. 반복문 카운터로 부동소수점형 변수를 사용하면 필연적으로 부동소수점 비교 연산을 수행하게 되는데 컴퓨터는 그 태생적 한계로 정확한 부동 소수점 값을 저장 및 비교하는 것이 불가능합니다. 따라서 부동소수점을 연산에 사용하면 반올림 혹은 버림 문제가 필연적으로 발생하기 때문에 정확한 연산이 필요한 곳에서는 사용을 자제해야 합니다.

에이, 저두 그건 알아요. 누가 그렇게 쓰나요? 라고 반문하신다면, 그 실제 사례를 소개하겠습니다.

 

KLDP를 뒤져서 한 가지 사례를 찾아냈습니다. 이 프로그래머는 반복문 카운터로 부동소수점 형의 변수를 사용했고, 이 때문에 1주일 동안 에러를 찾아내느라 고생했습니다.

어떤 프로그래머가 for문 안의 반복문 카운터를 부동소수점 형으로 사용했었던 것입니다. 몇 가지 가정하에 위 상황을 재현하기 위해서 C++로 코드를 작성했습니다.

가정 1. for 반복문은 1000번 돌면서 작업을 수행
가정 2. 프로그래머는 루프 카운터로 작업을 수행

#include <iostream>

using namespace std;

int main()
{
float f32 = 0.0F;
int i = 0;
for(f32 = 0.0; f32 < 100.0F; f32 +=0.1F)
{
cout<<"f32 : "<<f32<<endl;
/* f32로 어떠한 작업 수행 */
i++;
}
cout<<"Result : "<<i<<endl;
return 0;
}

프로그래머는 i에 100이 들어갈 것이라고 기대했을 것입니다. 그러나 결과는 어떨까요?

 

결과는 정확하게 100이 나오지 않습니다. 위 코드에서 부동소수점 비교 연산을 수행한 것은 프로그래머의 실수이며, JSF나 MISRA-C의 규칙을 위반한 대표적 사례입니다. JSF AV C++ 을 비롯한 많은 코딩 표준에서는 부동 소수점 변수를 직접적으로 비교하지 못하도록 규정하고 있습니다. 부동 소수점 자료형은 연산 과정에서 그 값이 딱 떨어지지 않기 때문입니다.

위 코드의 연산 결과를 봐도 0.1씩 997번 더하면 99.7이 아니라 99.6991이 나오는 것을 확인할 수 있습니다. 여기서 프로그래머가 true를 기대하고 if(f32 == 99.7) 이렇게 비교문을 작성하면 시스템에 따라 false 가 나올 수 있습니다.

지금 위의 결과도 Intel CPU(Intel Core2 Duo) 상에서 GCC(3.2, mingw)로 수행해서 이런 결과가 나오는 것 뿐, FPU나 Arithmetic이 다른 CPU혹은 다른 컴파일러에서 수행을 한다면 같은 결과가 나온다고 장담을 할 수 없습니다. 이런 유형의 오류는 일일이 Breakpoint 를 걸고 값을 확인하지 않는 이상 찾기도 힘듭니다. 만약 코딩 규칙을 염두에 두고 코드를 작성했다면 KLDP에 사연을 적었던 프로그래머는 디버깅하느라 일주일을 헛되이 쓰지는 않았을 것입니다.

또 하나 방법이 있습니다. 바로 정적 분석 도구를 사용해서 사전에 해당 오류를 잡아내는 것인데요, 정적 코드 분석 도구 스패로우에서는 본 포스팅에서 언급한 오류 유형에 대해서 검출 할 수 있습니다.

- BAD_EQUALITY_EXPRESSION.FLOAT : 부동 소수점 간의 비교 연산을 수행하는 코드를 탐지합니다.
- DO_NOT_USE_FLOAT_AS_LOOP_COUNTERS : for 문에서 부동소수점을 카운터로 쓰는 코드를 탐지합니다.

부동소수점 비교 방법

그렇다면 부동소수점을 꼭 비교해야 할 일이 생기면 어떻게 해야할까요? 자바의 경우는 라이브러리에서 비교 함수를 제공합니다. 이 글을 참조하시구요, C/C++의 경우 부동소수점의 정확도를 가지고 비교하는 방법을 사용합니다.


#include <math.h> //in C
#include <cmath> // in C++

#define EPSILON 0.00001 //정확도.

bool float_compare(float a, float b)
{
return fabs(a-b) < EPSILON; // 앞서 정의한 0.00001 이하는 비교하지 않습니다.

 

 

 해당 글은 파수닷컴에서 아이티데일리 게재용으로 제공한 콘텐츠입니다.
저작권은 파수닷컴 측에 있으며, 무단전제 및 복제를 금합니다.
원문출처 : http://blog.fasoo.com/80209918587

저작권자 © 아이티데일리 무단전재 및 재배포 금지