BASHA TECH

예상했던 실수 값이 출력되지 않는 문제 - 부동소수점 오차 본문

Error

예상했던 실수 값이 출력되지 않는 문제 - 부동소수점 오차

Basha 2022. 9. 14. 17:48
728x90

정수부의 크기 차이만 있을 뿐 소수부 숫자는 동일 하다. 근데 왜 값이 다른 걸까?

=> cpu 처리 과정에서 오차가 생김. 어느 언어나 이러한 이슈가 생김. 이것을 부동 소수점 오차라고 한다.

 

부동 소수점 오차는 무엇일까? 그러기 위해서는 컴퓨터가 실수를 표현하는 방식에 대해 먼저 알아야할 것이다.

컴퓨터가 실수를 표현하는 방식에는 대표적으로 고정 소수점(fixed point) 방식과 부동 소수점(floating point) 방식이 있다.

 

실수는 보통 정수부와 소수부로 나눌 수 있다. 

고정 소수점 방식은 소수부의 자릿수를 미리 정하고 고정된 자릿수의 소수를 표현하는 것이다.

장점은 실수를 부호, 정수, 소수부로 표현하여 단순하고 모든 수를 오차 없이 정확하게 표현할 수 있다. 하지만 표현 범위가 너무 적다는 단점이 있고 (소수부 고작 16bit뿐, 본래 10자리를 표현할 수 있던 자료형의 5자리를 떼어 소수부에 나눠준다면 자료형이 표현할 수 있는 수의 범위는 정수부 기준으로 5자리밖에 남지 않는다.) 또한 정밀도가 낮다. 따라서 고정 소수점 방식은 높은 정밀도가 필요없는 소규모 시스템에서만 간혹 쓰인다. (거의 안 쓴단 얘기)

 

이에 대한 단점을 보완하기 위해 나온 것이 부동 소수점 방식이다.

부동 소수점 방식은 실수를 정수부와 소수부가 아닌 가수부와 지수부 나누어 표현하는 방식을 이용한다.

이렇게 표현 하게 되면 정수부와 소수부의 크기가 미리 정해져 잇지 않아 훨씬 다양한 표현을 할 수 있게 된다. 즉, 표현할 수 있는 수의 법위가 넓어지게 되는 것.( 위의 123.456의 예시처럼 한 가지 숫자를 다양하게 표현할 수 있다는 점이 한편으로는 표현법이 중복된다는 이야기이기 때문에 비효율적이기도 하고, 오차가 발생할 수 있어 정확도가 떨어질 수 있다는 단점. 또한 지수가 커질수록, 가수 패턴 사이의 값 차이가 커져서 표현 불가능한 패턴이 점점 많아질 수 있다는 문제도 있다.)

 

부동 소수점 방식은 실수를 a*2^b 형식으로 저장한다. 이때 a는 1보다 크거나 같고, 2보다 작은 실수이다. 

이렇듯  지수부와 가수부를 통하면 고점 소주점 방식보다 큰 크기의 수를 표현 할 수 있다. (float형 기준, 가수부가 23bit나 되서 충분히 큰 수를 표현 할 수 있는데다가 지수부가 따로 있으므로 2^127,, 즉 10^38 정도의 큰 수 까지 표현가능)

또한 매우 작은 숫자까지 비교적 정확하게 표현하는 것이 가능하다. 하지만 단점은 전체 공간의 상단한 부분을 지수부에 떼어주었기 때문에 이또한 그만큼 표현할 수 있는 유효 숫자의 개수가 줄어든다. 

 

이처럼 부동 소수점은 크기가 제한된 메모리 공간에서 표현할 수 있는 수의 범위를 늘리기 위해 사용하지만, 대신 정확도는 떨어질 수 있다. IEEE(미국 전자전기 공학회)에서는 부동소수점 연산에서의 낭비를 최소화하고, 같은 비트를 쓰더라도 최대한 정확하게, 정밀도 높게 하기 위해 IEEE 754라는 표준을 정해두기도 했다.

 

IEEE 754는 부동 소수점 연산에 대한 표준이다. 이 표준이 어떻게 연산의 낭비를 줄이고 정확도를 높인다는 것일까? 가수를 조정해서 맨 앞에 0이 오지 않게 만드는 정규화 방식을 사용하거나, 가수의 맨 왼쪽에는 무조건 1이 올것이기 때문에 이를 생략하고, 다른 부분에 생략한 만큼의 공간을 더 사용하는 방식을 사용하기도 한다. 일단 가장 많이 쓰이는 두 가지 부동소수점 형식에 대해 알아보도록 하자. 기본정밀도(Single precision) 부동소수점수와, 2배정밀도(Double precision) 부동소수점 수 형식이다.

 

기본정밀도 형식 (Single precision)

우선 기본정밀도 형식은 32비트의 크기를 사용한다. 이 32비트 중 부호에 1비트, 지수부에 8비트, 가수부에 23비트를 사용한다. 프로그래밍 언어 중 다수의 4byte float 형식이 이 방식을 사용하고 있다.

 

 

2배정밀도 형식 (Double precision)

2배정밀도 형식은 기본의 두배인 64비트의 크기를 사용한다. 이 중 부호에 1비트, 지수에 11비트, 가수부에 52비트를 사용한다. 지수부 같은 경우에는 기본정밀도형식보다 3비트가 더 큰데, 이 말은 결국 2^3 = 8배의 범위 수를 표현할 수 있다는 것이다. 가수부는 기본정밀도형식보다 29비트가 더 커서 2^29 배 많은 수들을 표현할 수 있다. 비록 기본형식보다 두배나 많은 크기를 사용하지만, 표현할 수 있는 범위가 훨씬 크기 때문에 정확성을 높일 수 있다는 장점이 있다.

 

 

 

부동소수점 방식은 고정소수점 방식보다 훨씬 더 많은 범위를 표현할 수 있지만, 그럼에도 10진수를 정확하게 표현하지는 못하기 때문에 항상 오차가 존재할 수 밖에 없다. 이 점을 항상 염두해두고, 상황에 따라 적절한 정밀도의 자료형을 선택해야 한다.

 

부동 소수점 방식 오차는 해당 공식을 사용하면 표현할 수 있는 범위는 늘지만, 10진수를 정확하게 표현할 수는 없다. (무한소수, 순환소수의 경우 가수부가 표현할 수 있는 비트 수를 넘어가게 되면 손실되는 부분이 생기기 때문, 실수 또한 이진수로 표현하기 때문에 가수부가 1/2^n 꼴로 표현되는 경우만 오차없이 정확하게 값이 계산된다.) => cpu 에러임

 

이처럼 컴퓨터에서 실수를 가지고 수행하는 모든 연산에는 언제나 작은 오차가 존재하게 된다. 이것은 모든 프로그래밍 언어에서 발생하는 기본 문제.

 

부정확성의 원인

IEEE-754 부동소수점 표준에 의해 정수, 소수 등과 같은 숫자가 2진으로 저장되기 때문에 나타납니다. 예를 들어 1,2,3과 같은 정수는 2진수로 표현이 가능합니다. 하지만 1/7(0.142857142857... ), 0.1과 같은 소수(무한, 순환, 유한)들은 2진수로 표현할 방법이 없기 때문입니다. 0.142857...을 소수점 몇 번째 자리 아래에서 반올림을 하여도 그것은 반올림을 한 값이지 0.142857... 과 같은 값은 아닙니다. 컴퓨터는 그 값과 가장 근사한 값을 반환하는 것이고, 이때 부동소수점의 부정확성이 나타납니다. 

 

float과 double은 이진 부동 소수점 연산을 수행합니다. 이는 넓은 범위의 값에 대해 정확도가 높은 근사치를 제공할 수 있도록 세심하게 설계된 연산이지만 정확한 결과를 제공하지는 않기 때문에 정확한 결과가 필요한 곳에는 사용하면 안됩니다. 

부정확성의 해결 방법

BigDecimal 자료형을 사용하면 정확한 값을 계산할 수 있습니다. 하지만 덧셈과 같은 기본적인 연산을 할 때에도 최소 2개의 BigDecimal 객체를 선언해야하기 때문에 리소스를 많이 잡아먹을 수 있다.

 

728x90
반응형
Comments