Technology/Computer Architecture

부동 소수점 방식에 대한 이해

ikjo 2022. 1. 19. 02:40

부동 소수점의 등장 배경

부동 소수점(floating point)는 원어 그대로 둥둥 떠다니는 소수점수를 의미한다. 이는 컴퓨터가 2진수밖에 인식하지 못하기 때문에 생긴 것이다. 사람은 직관적으로 무한한 수를 제외한 어느 소수점의 수도 인식할 수 있다. 예를 들면, 사람은 십진수 0.1을 있는 그대로 0.1로 인식하고 표현할 수 있다. 하지만 컴퓨터는 2진수만을 인식하므로 2진수를 통해서 간신히(?) 십진수 0.1을 표현할 수 있다. 즉, 1/2, 1/4, 1/8, 1/16, ... 등의 2진수 조합으로 0.1을 만들어야 하는 것이다. 아주 작은 소수들을 무한에 가까울 정도로 합쳐 0.1에 거의 근접한 수(이진 무한 소수점수)를 만들 수 있겠다만 문제는 컴퓨터의 저장 공간은 유한하다는 것이다.

 

즉, 컴퓨터는 32비트(float) 또는 64비트(double) 메모리에 이러한 이진 무한 소수점수를 우겨넣어야하는 상황이 필연적으로 발생하게 된 것이다. 이때 이진 무한 소수점수를 어떻게 하면 효율적으로 우겨넣을 수 있는지에 대한 방법론으로 고정 소수점 방식이 생겼고 이 고정 소수점 방식의 문제점을 개선하기 위해 생긴 것이 바로 부동소수점 방식인 것이다. 

 

 

고정 소수점 방식

고정 소수점 방식은 부호를 표시하는 최상위 비트와 정수를 표현하는 비트 수 그리고 소수를 표현하는 비트 수를 미리 정해 놓고 해당 비트 만큼만 사용해서 숫자를 표현하는 방식으로 상당히 직관적인 방식이다. 예를 들어 4byte(float)를 사용하여 실수를 표현한다면 부호를 표기하기 위해 1bit, 정수 15bit, 소수 16bit를 사용한다고 가정하여 표현하는 식이다. 이런 식으로 153.3을 표현하면 (0)0000000010011001.010011001100110이 된다.(이진 기수법 참고)

 

이미지 출처 : https://madplay.github.io/post/the-need-for-bigdecimal-in-java

 

하지만 이러한 방식으로는 정수를 표현하는 bit를 늘리면 큰 숫자를 표현할 수 있지만 정밀한 소수점 표현이 힘들어진다. 그렇다고 소수를 표현하는 bit를 늘리면 정밀한 소수점 표현은 가능해지지만 큰 숫자를 표현할 수 없게 된다. 소수점을 이렇게 고정시켜버리니 이러한 문제가 발생한 것이다. 그리하여 이렇게 고정하지 않고 소수점을 둥둥 떠다니게 하는 부동 소수점 방식이 생겼다.

 

 

부동 소수점 방식 - IEEE 754

앞서 고정 소수점 방식은 153.3을 표현하기 위해 단 하나의 표현만 가능했다면 부동 소수점 방식은 1.533 * 10^2, 15.33 * 10^1, 1533 * 10^-1 이러한 방식으로 마치 소수점이 둥둥 떠다니듯이 여러가지의 표현이 가능하다는 특징이 있다.

 

이처럼 부동 소수점을 표현하는 방식은 다양한 방식들이 있지만 전기전자기술자협회(IEEE)에서 개발한 IEEE 754 방식컴퓨터에서 부동소수점을 표현하는 가장 널리 쓰이는 표준이다. IEEE 754의 부동 소수점 표현은 크게 세 부분으로 구성되는데, 부호를 표시하는 최상위 비트와 지수 부분(exponent)가수 부분(fraction/mantissa)이 있다. 이때 부호 비트는 1bit, 지수 비트는 8bit, 가수 비트는 23bit로 구성된다. 부호 비트는 말그대로 부호를 나타내며(0 : 양수, 1 : 음수) 지수 비트와 가수 비트는 다음 문단에서 IEEE 754 방식을 적용한 예시에서 상세히 다루었다.

 

 

이미지 출처 : https://steemit.com/kr/@modolee/floating-point

 

앞서 언급했듯이 부동소수점은 지수와 가수를 이용해서 소수점을 옮겨가며 표현할 수 있는데 IEEE 754 방식은 맨 앞에 1자리만 놔두고 나머진 다 소수점 이하로 내려서 표현(정규화된 부동소수점 수)하는 것이다. 이때 소수점 이하의 비트를 가수 비트라고 하며, 2 제곱의 지수를 이용하여 지수 비트를 나타낸다.

 

예를 들어 -118.625(십진법)을 IEEE 754 방식으로 표현해 보자. 우선 음수이므로 부호 비트는 1이다. 그 다음 118.625를 이진법으로 나타내면 1110110.101이 된다. 이때 맨 앞 1자리만 놔두고 소수점을 왼쪽으로 이동시켜 1.110110101×2⁶(6칸 앞으로 갔으닌까)을 만든다. 이때 가수 비트는 총 23비트이므로 나머지 부분은 0으로 채워넣는다. 결과는 1.11011010100000000000000×2⁶이 된다. 아울러 지수 비트는 지수 값 6에 바이어스(Bias) 상수(127)를 더해서 8bit 바이어스 표현방법으로 나타낸다. 결과는  6 + 127 = 133(1000 0101)으로 이를 8bit 형식의 2진수로 나타내면 10000101이 된다. 이 결과를 표로 나타내면 다음과 같다.

 

 

이미지 출처 : https://ko.wikipedia.org/wiki/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90

 

※ 잠깐! 바이어스 상수 127이란??

바이어스 상수를 더하는 이유는 지수 값이 음수가 될 수도 있기 때문이다. 앞서 부동소수점의 부호 비트는 전체 숫자의 부호이므로 지수 값의 부호와는 무관하다. 예를 들어 0.000101이라는 이진수를 부동소수점으로 표현하기 위해 정규화시키면 1.101*2*^(-4)가 되는데, 이때 지수 값 -4를 지수 비트로 표현하기 위함이다.

 

지수부가 8비트인 부동 소수점을 지수값에 따라 바이어스 표현법으로 나타내면 다음과 같은데 기존의 2진수 방식과는 다르다.

 

바이어스 표현법 : 지수

0000 0000 : -127  ※ 참고 : 0을 표현하기 위해 예약
0000 0001 : -126
...

0111 1011 : -4

0111 1100 : -3

0111 1101 : -2

0111 1110 : -1
0111 1111 : 0
1000 0000 : 1
...
1111 1110 : 127
1111 1111 : 128  ※ 참고 : 무한대를 표현하기 위해 예약

 

이때 바이어스 표현법은 지수 값을 2의 보수로 표현한 값에 바이어스 상수를 더해서 표현할 수 있다. 즉, 지수부가 n비트일 때는 지수 값에 2^(n-1) - 1을 더하여 부동 소수점을 나타내는데, 이 2^(n-1) - 1가 바로 바이어스 상수이다. 예를 들어 지수부가 8비트일 때는 127이 바이어스 상수인 것이다.

 

앞서 -4는 바이어스로 표현 시 0111 10111임을 확인할 수 있었는데 이를 검증해보자. -4에 127을 더한 값은 123이다. 이때 123을 2진수로 나타낸다면 0111 10111로 바이어스 표현법과 일치함을 확인할 수 있다.

 

그렇다면 부동 소수점 방식은 고정 소수점 방식과 비교하여 어떤 장점이 있을까? 바로 동일한 비트 수 대비 부동 소수점 방식이 고정 소수점 방식 보다 표현 가능한 수의 범위도 많고 정밀도 측면에서도 우수하다는 것이다. 부동 소수점 방식은 (가수)×(밑수 2)^(지수, 최대 128)와 같은 곱셈 형태로 표현되어 고정 소수점 방식(최대 2^15 -1) 보다 더 큰 수를 표현해줄 수 있다. 아울러 가수 부분을 확장하여 더 정밀한 소수점을 표현(고정 16bit → 부동 23bit)할 수 있다.

 

 

부동 소수점의 한계

하지만 이러한 부동 소수점의 방식에도 결국에는 한계가 있다. 다음 코드를 확인해보자

 

class Main {
    public static void main(String args[]){
        float num = 0f;
        for (int i = 0; i < 200; i++) {
            num += 0.1;
        }
        System.out.println(num);
    }
}

 

위 코드에서는 0.1을 200번 더해주었으므로 num의 최종 값은 정확히 20을 기대해볼 수 있겠지만, 실제 결과값은 20.00004이다. float 자료형의 크기는 4Byte(32bit)로 가수부의 크기가 23bit인데 부동 소수점을 표현 시 23bit를 초과하는 경우 초과하는 소수점을 표현할 방법이 없기 때문이다. 만일 보다 더 정밀하게 소수점을 표현하고 싶다면 크기가 8Byte(64bit)인 double 자료형을 사용해볼 수 있다. 이는 가수부의 크기가 52bit에 해당한다.

 

하지만 은행, 증권사 등 금융 도메인에서 돈과 관련한 데이터를 처리할 때는 99.99999%의 정밀도가 아니라 100%의 정밀도가 필요하다. 이때는 Java 언어 기준 BigDecimal 클래스를 사용해야 한다. BigDecimal은 Java 언어에서 숫자를 정밀하게 저장하고 표현할 수 있는 유일한 방법이다. BigDecimal 클래스는 내부적으로 배열을 사용하여(정수 = int[], 실수 = char[]) 수치 데이터를 분리 및 저장하므로 수치 데이터 자릿수에 제한이 없지만 다소 연산 속도가 느리다는 단점이 있다.

 

 

참고 자료

  • 유튜브 개발자 라라
  • https://steemit.com/kr/@modolee/floating-point
  • https://ko.wikipedia.org/wiki/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90
  • https://thrillfighter.tistory.com/349
  • https://gsmesie692.tistory.com/94