프로그래밍에서 실수형의 부정확성

실수형의 부정확함(?)에 대해서 생각해보다가, 어떤 분의 블로그를 보게 되었다.
우리는 아래의 문제에 대한 답을 생각해볼 수 있다.

(1)
double a = 0.1 + 0.1;
double b = 0.2;
if (a == b)
    printf("same");
else
    printf("different");

Output : ?

(2)
double a = 0.1 + 0.2;
double b = 0.3;
if (a == b)
    printf("same");
else
    printf("different");

Output: ?

(3)
double a = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
double b = 1.0;
if (a == b)
    printf("same");
else
    printf("different");

Output : ?





위의 문제의 답은 (1) same, (2) different (3) different 이다.
이 이유에 대해서 간단히 말하면, 컴퓨터의 모든 명령은 0과 1로 이루어진 이진수로 동작하기 때문이다.
즉, 실수 표현도 이러한 이진수로 표현하려고 하기 때문에 오차가 발생할 수 있는 것이다.
(컴퓨터 구조마다 이진수의 오차를 처리하는 방법에 따라 결과값이 다를 수 있다.)
위의 문제를 직접 이진수로 표현해보자.

0.1 = 1/16 + 1/32 + 1/256 + ...
계산하다보면 알겠지만, 절대 정확하게 나타낼 수가 없다.
0.5와 같이 딱 떨어지는 값은 나타낼 수 있겠지만, 무수히 많은 실수가 오차를 갖는다.
위와 같이 10진수 체계에서 딱 떨어지는 값이여도 2진수 체계에서 무한한 길이를 갖는다면,
오차가 생기는 것을 알 수 있다.
확장해서 무한소수를 이진수로 표현하려고 들 때도 오차가 생긴다는 것을 알 수 있다.
(0.99999... = 1처럼 순환하는 무한소수중 일부는 예외)

따라서 무작정 실수를 if (a == b) 라고 비교하려 하는 것은 위험하다.
왜냐하면 실수에 해당 값을 초기화했다고 해서, 컴퓨터에서는 수학적인 의미의 엄밀하게 같은 숫자가 아니기 때문이다.
컴퓨터가 동작하는 원리에 의해, 실수형을 저장할 때에는 아주 아주 작은 오차를 갖는 근삿값을 저장하기 때문이다.
따라서 실수형을 비교할 때에는 허용하는 범위내에서 오차를 빼주는 값으로 비교해주도록 하자.
이러한 원리에 의해 실수의 사칙연산도 근삿값으로 이루어진다는 것을 유의하자.

추가적으로, 정수형을 실수형(또는 실수형을 정수형)으로 변환할 때 주의해야 한다.
C++의 기준으로 숫자를 담는 자료형에 대한 유효숫자를 0의 개수를 기준으로 정리해보았다.
char (1byte) : 2^7 - 1  =>  127
short (2byte) : 2^15 - 1  =>  32767
int (4byte) : 2^31 - 1  =>  10^9 x 2.1
long long (8byte) : 2^63 - 1  =>  10^18 x 9.2
float (4byte) : 2^-23 => 10^-7 x 1.19
double (8byte) : 2^-52 => 10^-16 x 2.22

하나의 예로 int 형을 float 형에 저장할 때나, long long 형을 double 형에 저장할 때 오버플로우가 발생할 수 있다.

댓글

이 블로그의 인기 게시물

[PS] BOJ 20543번 폭탄 던지는 태영이

프로그래머스 2019 윈터코딩 온라인 테스트를 보았다. (풀이)