85.4 실수 자료형의 오차
부동소수점 방식은 실수를 정확히 표현할 수 없는 문제가 있습니다. 다음 내용을 소스 코드 편집 창에 입력하고 실행해봅니다.
float_rounding_error.c
#include <stdio.h> int main() { float num1 = 0.0f; float num2 = 0.1f; // 0.1을 10번 더함 for (int i = 0; i < 10; i++) { num1 = num1 + num2; } printf("%.15f\n", num1); // 1.000000119209290: 1.0이 나와야 하지만 반올림 오차 발생 return 0; }
실행 결과
1.000000119209290
분명 0.1을 10번 더했으므로 1.0이 나와야 하는데 1.000000119209290이 나왔습니다. 왜냐하면 수학적으로 실수는 무한히 많은데 이 실수를 유한 개의 비트로 표현하기 위해서는 근삿값으로 표현해야 하기 때문입니다. 이런 문제를 부동소수점 반올림 오차(rounding error)라고 합니다.
printf에서 소수의 자릿수를 좀 더 많이 표시하고 싶다면 %.15f처럼 서식 지정자에 자릿수를 지정해주면 됩니다. 여기서는 15를 지정했으므로 소수점 이하 15자리를 표시합니다.
다음과 같이 실수는 연산한 값을 == (같음)으로 직접 비교하면 안 됩니다. 이 부분은 초보들이 흔히 하는 실수입니다. 꼭 기억해두세요.
float_equality_comparison.c
#include <stdio.h> int main() { float num1 = 0.0f; float num2 = 0.1f; // 0.1을 10번 더함 for (int i = 0; i < 10; i++) { num1 = num1 + num2; } // num1: 0.100000001490116 if (num1 == 1.0f) // 반올림 오차가 발생하므로 실수는 ==로 비교하면 안됨 printf("true\n"); else printf("false\n"); // num1은 1.0이 아니므로 false 출력 return 0; }
실행 결과
false
이미 연산한 결과에서 반올림 오차가 발생해버렸기 때문에 등호로 직접 비교하면 잘못된 결과가 나오게 됩니다.
오차를 감안하여 실수를 비교하려면 다음과 같이 FLT_EPSILON을 사용해야 합니다.
float_epsilon_comparison.c
#include <stdio.h> #include <float.h> // float의 머신 엡실론 값 FLT_EPSILON이 정의된 헤더 파일 #include <math.h> // float의 절댓값을 구하는 fabsf 함수를 위한 헤더 파일 int main() { float num1 = 0.0f; float num2 = 0.1f; // 0.1을 10번 더함 for (int i = 0; i < 10; i++) { num1 = num1 + num2; } // num1: 1.000000119209290 if (fabsf(num1 - 1.0f) <= FLT_EPSILON) // 연산한 값과 비교할 값의 차이를 구하고 절댓값으로 // 만든 뒤 FLT_EPSILON보다 작거나 같은지 판단 // 오차가 머신 엡실론 이하라면 같은 값으로 봄 printf("true\n"); // 값의 차이가 머신 엡실론보다 작거나 같으므로 true else printf("false\n"); return 0; }
실행 결과
true
먼저 연산한 값과 비교할 값의 차이를 구한 뒤 FLT_EPSILON보다 작거나 같은지 판단합니다. 값의 차이는 math.h 헤더 파일의 fabsf 함수를 사용하여 절댓값으로 만들면 음수가 나오더라도 정상적으로 판단할 수 있습니다.
FLT_EPSILON은 float.h 헤더 파일에 정의되어 있으며 이 값을 머신 엡실론(machine epsilon)이라 부릅니다. 어떤 실수를 가장 가까운 부동소수점 실수로 반올림하였을 때 상대 오차는 항상 머신 엡실론 이하입니다. 즉, 머신 엡실론은 반올림 오차의 상한값이며 연산한 값과 비교할 값의 차이가 머신 엡실론보다 작거나 같다면 두 실수는 같은 값이라 할 수 있습니다. 만약 double, long double을 사용한다면 머신 엡실론은 DBL_EPSILON, LDBL_EPSILON을 사용합니다.
엑셀 2007에서는 850 *77.1을 계산하면 65535가 나와야 하지만 100000이라는 수가 나오는 버그가 있습니다. 부동소수점의 특성으로 인해 숫자를 문자로 바꾸는 도중 버그가 발생한 재미있는 사례입니다. 버그에 대한 자세한 분석 내용은 조엘 온 소프트웨어 웹 사이트에서 볼 수 있습니다.
- Explaining the Excel Bug: http://www.joelonsoftware.com/items/2007/09/26b.html