728x90

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)이라 부릅니다. 어떤 실수를 가장 가까운 부동소수점 실수로 반올림하였을 때 상대 오차는 항상 머신 엡실론 이하입니다. 즉, 머신 엡실론은 반올림 오차의 상한값이며 연산한 값과 비교할 값의 차이가 머신 엡실론보다 작거나 같다면 두 실수는 같은 값이라 할 수 있습니다. 만약 doublelong double을 사용한다면 머신 엡실론은 DBL_EPSILONLDBL_EPSILON을 사용합니다.

읽을거리

엑셀 2007에서는 850 *77.1을 계산하면 65535가 나와야 하지만 100000이라는 수가 나오는 버그가 있습니다. 부동소수점의 특성으로 인해 숫자를 문자로 바꾸는 도중 버그가 발생한 재미있는 사례입니다. 버그에 대한 자세한 분석 내용은 조엘 온 소프트웨어 웹 사이트에서 볼 수 있습니다.


+ Recent posts