부동소숫점 오류

게시판에 올라오는 "이 코드는 왜 틀렸나요?" 류의 질문 중 부동소숫점 오류 때문에 틀리는 경우가 꽤 많이 보여서

간단하게 몇 가지 써봅니다.


0. 부동소숫점 오류

모든 실수를 8byte, 혹은 12~16byte의 변수에 모두 담을 수 있다고 생각하시는 분은 없겠죠.

변수에 실수를 저장할 때는 어느 정도의 정보 손실이 일어날 수밖에 없습니다.

절대 잊어서는 안되는 것은, 실수 변수는 절대 정확한 값을 가지고 있지 않다는 것입니다.


1. 오차때문에 틀리는 예시 몇가지


1) 문제를 풀 때는 float보다는 double형 변수를 쓰는게 좋습니다.

double형 변수까지는 하드웨어로 계산되기 때문에 많이 느려지지 않지만, 정확도가 엄청나게 높아지기 때문입니다.

(float의 상대오차는 약 10^-7 정도이고, double의 상대 오차는 약 10^-15 정도입니다)

long double (12bit 혹은 16bit)는 소프트웨어의 도움을 받기 때문에 꽤 많이 느려지기 때문에 쓴다고 꼭 좋은 것은 아닙니다.


2) 정수가 들어있는 실수형 변수를 정수로 바로 캐스팅하면 안됩니다.

1을 double 변수에 대입하면 0.9999... 같은 이상한 숫자가 됩니다. 이 변수를 그대로 정수로 캐스팅한다면 0이 되겠죠.

보통의 경우에는 1e-6 ~ 1e-9 정도를 더해서 캐스팅을 하거나,

정수형 변수만을 사용하여 연산을 하기도 합니다.

( scanf("%d.%d")으로 입력을 받고, printf("%d.%02d", a/100, a%100)으로 출력하는 식으로.

반올림이 필요하다면 나머지 연산을 사용해야 합니다.)


3) 비교 연산을 할 때는 등호를 사용하시면 안됩니다.

실수형 변수는 오차가 있기 때문에 같은 값을 가져야만 하는 상황에서도 다른 값일 때가 매우 많습니다.

보통의 경우에는 abs(A-B) < EPS, EPS는 1e-6~1e-9 정도로 정합니다.


4) 큰 수를 다룰 때, 매우 작은 상수값을 사용하는 것은 위험할 수 있습니다.

double형의 상대 오차는 10^-15입니다. 즉, 10^15를 double형 변수에 대입하면 오차가 1의 자리에서 발생할 수 있습니다.

즉, 조건문으로 (A-B) < 1e-6 을 사용했는데, A, B가 10^11 크기 정도라면,

(A-B) < 1e-6과 A == B는 똑같은 결과를 만들게 됩니다.

double형 변수를 가지고 넓은 범위의 이진탐색을 돌릴 때 자주 발생하는 문제이고,

100~200번 정도만 반복한다던가, 상대오차가 몇 이하일 때 반복문을 빠져나오는 식으로 해결합니다.


5) 큰 수에 작은 수를 더할 때 조심해야 합니다.

예를 들어, 10^20 정도 되는 double형 변수에 1을 10^20번 더해도 값이 변하지 않습니다.

또는, 큰 수에 작은 수를 더할 때 작은 수의 정밀한 부분이 사라지기 때문에 오차가 커질 수 있습니다.

보통은 작은 수끼리 더한 뒤에 큰 수에 더하는 방법으로 해결이 가능하나,

이런 것 때문에 틀리는 문제는 극히 드뭅니다.

(연습문제: https://www.acmicpc.net/problem/1196 )


2. 오차의 전파

1등을 가르는 문제가 아닌 이상 오차를 계산하거나, 줄여야만 하는 문제는 아예 안나오니까

1-3)에서 나온 EPS를 계산하고 싶거나 궁금하신 분들만 보세요.

오차 식은 검색하면 나오는 일반물리학실험 식을 그대로 가져왔습니다.

z가 왼쪽 식처럼 계산될 때, 오차가 오른쪽처럼 계산된다는 뜻입니다.


(출처)


double형 변수가 10^-15 정도의 오차를 가지고 있으니까, 처음 변수의 크기를 x, 오차를 x * 10^-15 정도로 계산한 뒤,

연산 한번 할 때마다 변수의 오차가 어떻게 변하는지를 계산하면 되겠습니다.

비교를 할 때는 오차를 잘 계산해서, EPS를 오차보다는 크게, 변수가 달라질 때 받는 영향보다는 작게 설정하면 완벽합니다.


3. 마치며,

1-2) 때문에 틀리는 경우가 매우 많습니다. 확인을 꼭 해주세요.

P.S 추가되거나 수정되어야 하는, 혹은 했으면 하는 내용을 자유롭게 적어주세요.


댓글 (5개) 댓글 쓰기


jays 7년 전

잘 읽고 갑니다. 좋은 글 남겨주셔서 감사합니다.


lety 7년 전

log sqrt 같은 math.h 헤더에 있는 함수들 쓸 때 실수


dongwon0427 6년 전

이거 보고 v 정렬했더니 맞았네요 감사합니다


dragonite 5년 전

    double myNum;
    myNum = 1;
    cout << "myNum = "<< myNum << endl;

    int c = myNum;
    cout << "c = " << c << endl;

이렇게 해도 myNum =1 , c=1 으로 출력되는데 제가 잘 못 이해한것인가용??


hyoukim 2년 전

0.000000000000001 단위 오차 발생요. 10의 -15제곱 오차