본문 바로가기
언어/CS

[CS] 부동소수점 오류

by Geunny 2020. 7. 22.
반응형

부동소수점....

분명 학부시절에 좀 들었던 내용이지만 하루에 배우는 양이 엄청나다 보니 귀에 들어 오지 않았을 뿐이다.

그러나 지금은 자바를 이미 어느정도 안다고 생각한 후 다시 기초부터 듣게되니

내가 몰랐던 내용들에 더 귀가 들어오고 있다.

그중 하나는 오늘 들은 부동 소수점 이야기이다.

먼저 예시로 다음 코드를 보자

        int apple = 1; // 사과 1개
        double pieceUnit = 0.1; // 사과를 10개로 쪼갠 조각 중 1개

        int num = 7;
        double result = apple - pieceUnit*num;
        System.out.println(result);

위 예시는 사과(apple) 1개를 숫자 1이라 치고 사과 조각(pieceUnit)을 0.1로 하여 먹은 사과수(num)를 입력하여 남아있는 사과를 구해보는 것을 숫자로 표현하였다.

 

사람의 머리로는 당연히! 1에서 0.1*7 을 빼면 기본적으로 사칙연산을 한 사람이라면 1-0.7 = 0.3 인것을 눈으로도 풀수 있다.

 

그러나 위의 코드를 실행해 보면...

0.00000......7 누가 먹었니..

이러한 결과가 나오는 이유는 컴퓨터가 실수를 저장할 때 가수부와 지수부로 나누어 저장을 하는데 (a)*2^b 꼴로 저장이 된다. 여기서 a 는 1보다 크거나 같고, 2보다 작은 실수이다. 즉, (1.xxx)*2^b 꼴로 저장이 된다.

여기서 실제 메모리에 저장될때는 지수부인 b 와 가수부인 a 가 각각 메모리에 다음과 같은 구조로 저장이 된다.

sign : 지수부 fraction: 가수부

여기서 가수부의 부분이 2진수로 표현하게되면 무한 소수로 표현되게 되어 메모리 범위를 벗어날 시 해당 데이터가 날라가게 되므로 실제 수와 오차가 생겨나게 된다.

수학적으로 증명을 하자면 정말 복잡하고 머리가 너무 아프지만 우리가 가우스같은 수학자는 아니니깐..

 

간단히 설명을 해보자면

 

실수의 크기는 정수보다 훨~~~씬 더 크다고 볼수 있다.

그러한 큰 크기의 무한의 실수를 8byte, 혹은 12~16byte 의 변수에 모두 담을 수 없다. 

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

따라서 여기서 절대 잊지 말아야 할 것은 실수 변수는 절대 정확한 값을 가지고 있지 않다는 점을 꼭 명심해야 한다.

 

아래의 내용들은 부동소수점 오류로 인한 애러를 줄이기 위한 주의점들 이다.

해당 글은 BAEKJOON 사이트의 글을 인용하였다.

https://www.acmicpc.net/blog/view/37

 

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를 오차보다는 크게, 변수가 달라질 때 받는 영향보다는 작게 설정하면 완벽합니다.

 

 

이것으로 오늘의 포스팅을 마침니다!

출처

https://codingdog.tistory.com/entry/

'언어 > CS' 카테고리의 다른 글

[CS] 논리 연산의 단락 평가 (short-circuit evaluation)  (0) 2020.07.23

댓글