고정소수점 방식과 부동소수점 방식 비교

개요

수치해석에서 수를 표현할 때 가장 중요한 문제 중 하나는 실제 연산 과정에서 발생하는 오차와 표현 가능한 수의 범위다. 어떤 방식으로 수를 표현하느냐에 따라 연산 정밀도와 범위가 달라진다. 이것은 알고리즘의 정확도뿐만 아니라 안정성에도 큰 영향을 미친다. 여기서는 고정소수점 방식과 부동소수점 방식의 기본 개념, 내부 표현, 연산 특성, 오차 발생 기작 등을 비교하면서 그 차이점을 엄밀히 살펴본다.

고정소수점 표현의 이해

고정소수점(fixed-point)은 소수점의 위치가 고정되어 있다는 특징을 가진다. 컴퓨터 내부에서 소수를 다루기 전에는 대부분 정수 연산만으로 계산을 수행했기에, 정해진 비트 수 안에 가정된 소수점 위치에 따라 정밀도를 확보하는 것이 일반적이었다. 예를 들어 16비트 고정소수점에서 상위 8비트를 정수부, 하위 8비트를 소수부로 정의하면, 입력된 값은 $a = i + f$로 표현할 수 있다. 여기서 $i$는 정수부, $f$는 소수부를 가정된 배율에 따라 비트 단위로 저장한다.

고정소수점 표현을 2진법으로 가정하여 설명하면, 어떤 수 $x$가 주어졌을 때

x=bn1bn2b1b0.b1b2bmx = b_{n-1} b_{n-2} \dots b_1 b_0 . b_{-1} b_{-2} \dots b_{-m}

와 같이 $n$비트 정수부와 $m$비트 소수부로 구성된다고 볼 수 있다. 실제 컴퓨터 메모리에 저장할 때는 $x$를 정수로 변환하여

Xint=x×2mX_{\text{int}} = x \times 2^m

으로 취급하고, 연산 시에는 $2^{-m}$을 곱하여 소수로 되돌리는 식으로 계산을 진행한다.

고정소수점은 구조가 단순하고 하드웨어 구현이 용이하다는 장점이 있다. 특히 임베디드 시스템이나 DSP(Digital Signal Processor) 등에서 널리 활용되어 왔다. 그러나 표현 범위가 제한되어 있고, 주어진 소수점 위치에서 배정밀도 이상의 정밀도를 확보하기가 어렵다는 한계가 있다. 오버플로와 언더플로 상황이 발생하기도 쉬우며, 소수점이 고정되어 있으므로 매우 큰 수나 매우 작은 수의 표현에 비효율적일 수 있다.

부동소수점 표현의 이해

부동소수점(floating-point)은 소수점의 위치가 고정되지 않고 유동적으로 움직이는 방식이다. 이는 $x$가

x=±m×Bex = \pm m \times B^e

와 같은 형태로 표현된다는 것을 의미한다. 여기서 $m$은 유효숫자(가수, mantissa), $e$는 지수(exponent), $B$는 밑(base)를 말한다. 실제로는 유효숫자의 길이, 지수의 범위, 정규화 방식, 부호 비트 등을 특정 규약으로 정의하여 실제 하드웨어에서 일관성 있게 구현한다.

부동소수점 방식은 과학 계산에서 매우 중요한 역할을 담당한다. IEEE 754 표준에 따르면, 단정밀도(single precision)에서는 32비트로 $1$비트의 부호, $8$비트의 지수, $23$비트의 가수를 사용한다. 이때 가수는 보통 정규화(normalization)를 통해 $\pm 1.\dots$ 형태로 저장한다. 이로써 보다 넓은 표현 범위와 정밀도를 확보할 수 있다.

부동소수점 연산에서는 라운딩(반올림), 정규화 과정 등을 거치게 되므로, 기계 오차(machine epsilon)라 불리는 최소 단위가 존재한다. 예를 들어 단정밀도에서 기계 오차는 $2^{-23}$ 수준이지만, 실제로는 지수부의 크기에 따라 유효숫자의 정확도도 동적으로 변화한다. 이러한 특성 때문에 소수점 이하가 매우 긴 값을 연산할 때, 혹은 서로 크기가 현격히 다른 수를 덧셈·뺄셈할 때 정밀도 손실이 발생할 수 있다.

고정소수점과 부동소수점의 장단점

고정소수점은 하드웨어적으로 구현이 간단하고 예측 가능성이 높다는 장점이 있다. 하지만 표현 범위가 작고 소수점 위치를 변경하기가 불가능하다는 점 때문에 다양한 스케일의 수를 다뤄야 하는 문제에서 불편함이 존재한다. 반면 부동소수점은 표현 범위가 유연하며 과학적 계산에 유리하지만, 연산 과정에서 정규화, 라운딩과 같은 부가 연산이 필요하며 작은 오차가 누적될 가능성이 있다. 메모리 사용량도 상대적으로 커질 수 있고, 구현 자체가 비교적 복잡하다는 단점도 존재한다.

부동소수점 표준과 정규화 방식

부동소수점 수를 다룰 때 일반적으로 사용하는 규약은 IEEE 754 표준이다. 이 표준에서는 부호 비트, 지수부, 가수부로 구성된 실수를 일정한 방식에 따라 이진수로 표현한다. 단정밀도(single precision)에서 32비트 중 1비트를 부호, 8비트를 지수, 23비트를 가수로 사용하고, 배정밀도(double precision)에서는 64비트 중 1비트를 부호, 11비트를 지수, 52비트를 가수로 할당한다. 이때 실제 내부 표현은 정규화(normalization) 방식을 통해 가수가 $\pm 1.\dots$ 형태를 이루도록 제약을 둔다.

가령 단정밀도 부동소수점 수가 주어졌을 때, 이를 내부적으로

x=(1)s×1.×2E127x = (-1)^{s} \times 1.\dots \times 2^{E - 127}

와 같은 형태로 해석한다. 여기서 $s$는 부호 비트, $E$는 지수부를 10진수로 환산한 값, $127$은 지수 바이어스(bias)다. 단정밀도에서는 바이어스가 127이며, 배정밀도에서는 1023으로 설정된다.

정규화된 수는 가수 맨 앞자리를 1로 고정하기 때문에, 가수부에 저장되는 23비트(혹은 52비트)는 소수점 이하 부분만을 나타낸다. 이를 통해 유효숫자의 정확도를 최대화하면서 한편으로는 지수를 자유롭게 조절할 수 있다. 이때 지수가 모두 0이 되어 정규화가 불가능한 상황이라면 비정규화(denormalized) 수로 표현한다. 비정규화 수는 지수부가 $0$에 가까운 매우 작은 실수를 표현할 때 활용되어, 정상적인 정규화 상태에서 나타날 수 없는 극도로 작은 수를 표현하기 위해 사용된다.

라운딩과 정밀도 손실

부동소수점 연산이 이루어지는 과정에서 실제로는 유효숫자(가수부)로 표현할 수 있는 비트 수에 한계가 있어, 연산 결과를 원래 비트 수에 맞추기 위해 반올림(라운딩) 과정을 거쳐야 한다. 일반적으로 IEEE 754 표준에서는 아래와 같은 여러 가지 라운딩 모드를 정의하고 있다.

대표적으로 라운드 투 니어리스트(round to nearest, ties to even) 방식이 많이 사용되는데, 이는 반올림 과정에서 가장 가까운 수로 맞추되 정확히 중간점에 있을 경우에는 짝수가 되도록 처리한다. 이렇게 정밀도를 유지하기 위해 의도된 반올림을 적용하지만, 실제 연산에서 매우 작은 오차가 누적되거나, 서로 크기가 크게 다른 두 수를 덧셈 또는 뺄셈할 때 상대적으로 큰 정밀도 손실이 발생할 수 있다.

예를 들어 $1.0 + 2^{-24}$ 정도의 연산을 단정밀도로 수행할 때, $2^{-23}$이 기계 오차(machine epsilon)의 스케일에 가깝기 때문에, 실제로는 $1.0$에 정확히 반영되지 않을 수 있다. 이처럼 부동소수점 방식에서는 수의 크기에 따라 유효하게 표현할 수 있는 자릿수가 달라지며, 이는 반올림 과정과 맞물려 필연적으로 작은 오차가 발생한다.

오버플로와 언더플로

고정소수점이든 부동소수점이든 표현 가능한 범위를 벗어나는 수가 들어오면 오버플로(overflow) 또는 언더플로(underflow) 현상이 발생한다. 고정소수점에서는 표현 범위가 매우 좁기 때문에, 조금만 큰 값을 다뤄도 쉽게 오버플로가 일어날 수 있다. 부동소수점은 지수가 자유롭게 변화할 수 있어 상당히 큰 수까지 표현할 수 있지만, 그래도 무한대(infinity)로 처리될 수밖에 없는 임계 범위 밖의 값이 존재한다.

반대로 매우 작은 수를 표현하려면, 부동소수점에서도 지수가 작아질 대로 작아져 결국 표현 불가능한 상황에 도달한다. 이때 0으로 언더플로가 일어날 수 있으며, 이는 계산 과정에서 예기치 않은 결과를 초래하기도 한다. 특히 반복 계산이나 누산(accumulation) 과정에서 언더플로가 누적되면 유효숫자가 급격히 감소하거나 0으로 떨어지면서 알고리즘의 안정성에 영향을 미칠 수 있다.

수치적 안정성과 대표적 오류 양상

고정소수점과 부동소수점 방식은 그 표현 범위와 정밀도에서 차이가 있으므로, 실제 계산 과정에서 나타나는 오류 양상도 다르다. 고정소수점에서는 비교적 단순한 양상의 오버플로나 언더플로가 자주 관찰되며, 스케일 변환이 필요할 때 수동으로 소수점 위치를 재조정해야 하는 불편함이 있다. 반면 부동소수점에서는 복잡한 라운딩 규칙과 정규화 과정 덕분에 훨씬 넓은 수 영역을 지원하지만, 상대적으로 작은 오차가 계속 누적될 수 있다는 특징이 있다.

수치적 안정성(numerical stability)은 알고리즘이나 연산 과정에서 이러한 오류 누적을 얼마나 억제하며 신뢰도 높은 결과를 보장하는지 평가하는 개념이다. 알고리즘이 안정적이면 입력값이 조금 바뀌어도 결과 오차가 폭발적으로 증가하지 않는다. 하지만 불안정한 알고리즘이라면 아주 작은 부동소수점 오차라도 반복 계산을 거치면서 크게 증폭될 수 있다. 예컨대 큰 수에서 작은 수를 빼거나, 반복 구조에서 차이가 미미한 두 수를 계속해서 덧셈·뺄셈하는 형태는 부동소수점 환경에서 안정성을 떨어뜨리는 전형적인 패턴이다.

고정소수점과 부동소수점 연산 예시

다음 예제는 서로 다른 방식으로 실수를 처리했을 때 어떤 결과가 나오는지 단순화한 시뮬레이션을 보여준다. 실제 하드웨어나 라이브러리를 모두 세세하게 재현하는 것은 복잡하므로, 여기서는 제한된 정밀도를 가정한 간단한 모형으로 비교한다.

다음 코드는 Python에서 부동소수점과 임의의 고정소수점 연산을 모사한 예시다. 파이썬의 기본 실수 연산은 IEEE 754 배정밀도(double precision)를 사용하므로, 우선 직접 $2^{-m}$ 스케일링을 적용하는 식으로 간이 고정소수점 연산을 흉내 내 보자.

위 예제에서 m=8일 때 고정소수점은 소수점 이하를 8비트, 즉 $2^{-8}=1/256$ 단위로 표현한다고 가정했다. 예시값 a_val=1.234375는 이 스케일로 정확하게 표현 가능한 수이지만, b_val=0.0009765625도 $1/1024$이므로 $2^{-10}$ 형태다. 이 경우 $m=8$일 때는 $1/256$의 배수가 되지 않으므로, 덧셈 과정에서 미세하게 차이가 발생한다. 실제 부동소수점 더하기와 비교해 보면 결과 값이 다소 다를 수 있다. 이러한 차이는 고정소수점에서의 스케일링 손실, 그리고 부동소수점에서의 라운딩 오차가 서로 달리 작용하기 때문이다.

축적 오차와 누적 오류

특히 반복 계산이 중요한 수치해석 알고리즘에서 작은 차이가 누적될 때 크게 달라질 수 있다. 고정소수점에서는 어느 시점에서 오버플로가 발생하거나 소수부가 사라져버리면 결과가 극단적으로 변하고, 부동소수점에서는 비정규화 수나 라운딩 오차가 계속 겹치면서 예기치 않은 결과가 나올 수 있다. 이런 상황을 방지하기 위해서는 변수 스케일을 적절히 조절하거나, 다중 정밀도(multiple precision) 라이브러리를 사용하는 등의 방법으로 오류를 제어한다.

확장 정밀도와 사후 처리

현대적인 부동소수점 하드웨어나 소프트웨어 라이브러리에서는 중간 계산을 더 높은 정밀도(예: 80비트, 128비트)로 수행하고 결과만 32비트 또는 64비트로 저장하기도 한다. 이를 확장 정밀도(extended precision)라 부르는데, 흔히 인텔 x87 부동소수점 레지스터가 80비트 정밀도 사용을 지원하고, 여러 언어에서 long double 형태로 80비트 이상을 지원하기도 한다. 이렇게 하면 중간 계산에서 발생하는 반올림 오차를 줄일 수 있다.

그러나 무조건 확장 정밀도를 쓴다고 해서 모든 문제가 해결되는 것은 아니다. 매우 큰 반복 계산, 조건수(condition number)가 큰 문제, 감쇠(attenuation)와 증폭(amplification)가 반복되는 알고리즘에서는 확장 정밀도도 결국 오차가 누적될 수 있다. 그래서 알고리즘 수준에서 수치적 안정성을 고려한 설계가 필수적이며, 경우에 따라선 임의 정밀도 연산(arbitrary precision arithmetic)을 제공하는 소프트웨어 라이브러리를 이용하기도 한다.

취소오차(cancellation error)와 오차 증폭 사례

부동소수점 연산에서 가장 빈번하게 문제를 일으키는 것 중 하나가 취소오차다. 크기가 비슷한 두 수를 뺄셈할 때, 유효숫자(mantissa) 상에서 상위 자릿수가 상쇄되며 유효하게 표현할 수 있는 자릿수가 급격히 줄어드는 현상이 발생한다. 예컨대 $x \approx y$인 상황에서 $x - y$를 계산하면, 결과는 미소한 값이지만 부동소수점 내부에서는 큰 수끼리의 마찬가지로 정확도가 제한된 상태로 연산이 이뤄진다. 그 결과, 상대적으로 큰 정밀도 손실이 나타날 수 있다.

이처럼 취소오차는 뺄셈에서 자주 관찰되지만, 연산 순서나 알고리즘 구조에 따라서 의외의 연쇄효과로 이어질 수 있다. 예를 들어 매우 작은 수를 큰 수에 반복해서 더하거나, 오랜 반복 과정 후에 두 값이 서로 비슷해질 때 마지막 단계에서 큰 오차를 유발하기도 한다. 이러한 문제를 방지하기 위해서는 가능한 한 취소오차를 유발하는 뺄셈을 피하거나, 미분 방정식 풀이 등에서 적절한 재배열(rearrangement) 기법을 적용하기도 한다.

대표적인 취소오차 예시

다음 코드는 간단한 예제를 통해 부동소수점에서의 취소오차 양상을 살펴보는 예시다. 두 값이 매우 비슷해질 때의 뺄셈 결과가 유의미한 손실을 유발하는지 살펴본다.

이 예시에서 $n$이 커질수록 $x$와 $y$의 차이가 작아져서, 뺄셈 시 상위 비트가 대량으로 소실된다. 결과적으로 이론상으로는 $1/n$인 값이 실제 부동소수점 연산에서 점차 부정확해지는 모습을 볼 수 있다.

연산 재배열(rearrangement)과 안정화 기법

수치해석에서 이런 취소오차를 줄이거나 피하기 위해, 연산 순서를 재배열하는 전략이 자주 쓰인다. 예를 들어 뺄셈 대신 적절한 분자·분모 변형을 통하여 뺄셈을 우회한다거나, 큰 값과 작은 값을 바로 더하지 않고 작은 값들끼리 먼저 더한 뒤에 큰 값에 가산하는 식으로 계산하는 것이 그 예다. 또한 합산 과정에서 Kahan Summation Algorithm처럼 부동소수점 오차를 보정해 주는 알고리즘을 이용하기도 한다.

아래 그림은 간단히 고정소수점과 부동소수점의 표현 방식을 비교하는 모식도다. 실제 하드웨어나 소프트웨어 구현은 훨씬 복잡하지만, 여기서는 지수부, 가수부, 스케일링 등의 개념 차이를 그림으로 나타냈다.

spinner

부동소수점 쪽은 지수를 조정하여 표현 범위를 넓게 확보하고, 정규화 과정을 통해 최대한 많은 유효숫자를 활용하지만, 복잡한 반올림 과정에서 오차가 누적될 수 있다. 고정소수점은 반대로 소수점 위치가 고정되어 예측성이 좋고 구현이 간단하지만, 표현 범위가 좁고 스케일을 직접 관리해야 하는 부담이 따른다.

혼합 정밀도와 고급 활용

수치 계산 현장에서는 모든 연산을 단일 정밀도로만 수행하는 대신, 목적과 상황에 따라 혼합 정밀도(mixed precision)를 활용하여 성능과 정확도를 함께 극대화하기도 한다. 예컨대 대규모 선형대수 문제에서 전처리(preconditioner)나 초기 근사값 계산에는 단정밀도를 사용하고, 마지막 정밀 보정 단계에서는 배정밀도나 그 이상의 확장 정밀도를 사용함으로써 연산 자원을 절약하면서 필요한 정확도를 확보하는 방식이다.

부동소수점 하드웨어가 발전함에 따라 GPU나 특수 코프로세서에서도 혼합 정밀도 알고리즘을 지원하는 추세가 늘었다. 예를 들어 NVIDIA GPU에서는 Tensor Core를 통해 반정밀도(half precision, 16비트)로 행렬 연산을 빠르게 수행하고, 필요한 단계에서만 단정밀도나 배정밀도로 보정 연산을 실시한다. 이런 혼합 정밀도 접근은 단순히 “짧은 비트 길이 = 부정확”이라는 통념을 깨고, 알고리즘적으로 안정적이면 매우 빠른 처리 속도와 충분한 정확도를 동시에 얻는 결과를 낳는다.

현실적인 대규모 계산 문제에서는 입·출력, 대규모 메모리 접근, 통신 비용 등이 매우 크므로, 문제 전반을 배정밀도만으로 처리하는 것이 늘 최선은 아니다. 오히려 알고리즘의 병렬화 특성과 데이터 리사이징(resizing) 정책을 적절히 설계해 혼합 정밀도를 적용할 때, 놀라운 성능 향상을 얻을 수 있다. 이때 부동소수점 특유의 라운딩·정규화 과정을 면밀히 추적하여, 오차가 누적되지 않도록 주기적으로 상위 정밀도로 보정하는 과정을 설계하는 것이 핵심 포인트다.

NaN, Infinity, Subnormal 등 특수값 처리

부동소수점 표준(IEEE 754)은 일반 실수의 범위를 넘어서는 특수값들도 정의한다. 예를 들어 지수가 최대값을 초과하여 오버플로가 발생하면 $+\infty$ 또는 $-\infty$로 처리되고, $0$으로 나눔 또는 음수의 제곱근 등 정의되지 않은 연산 결과는 NaN(Not a Number)으로 표현된다. 또, 지수가 모두 0이면서 가수가 0이 아닌 값은 비정규화 수(subnormal number)로 취급되어, 정규화가 불가능하지만 표현 가능한 최소 범위 바깥의 미세한 양을 담당하게 된다.

이런 특수값은 계산 과정에서 예측치 못한 사례를 일으킬 수 있지만, 한편으로는 디버깅이나 예외 처리에 유용한 정보를 제공하기도 한다. 예를 들어 반복 알고리즘에서 NaN이 발생하면, 중간에 어떤 연산이 불가능한 값을 생성했음을 즉시 알 수 있으며, $\pm\infty$가 생성되면 오버플로가 발생했음을 명확히 알게 된다. 고정소수점 방식은 일반적으로 이런 특수값을 자동으로 처리해 주지 않고, 표현 범위 초과 시 하드웨어 의존적으로 잘못된 값이 산출되거나 최댓값·최솟값 근처로 클리핑(clipping)될 수 있다.

고정소수점과 부동소수점의 하드웨어 성능 차이

현대 CPU, GPU, DSP 등에서 부동소수점 장치는 대부분 표준화된 회로(예: FPU, FP ALU 등)로 구성되어 매우 빠른 속도로 연산을 수행할 수 있다. 특히 최근에는 벡터화(vectorization)와 병렬화 기술이 발전하여, 단정밀도 또는 배정밀도 부동소수점 연산을 대규모로 처리할 수 있는 SIMD, AVX, GPU 코어 등이 보편화되었다.

반면 고정소수점 연산은 전용 하드웨어가 없다면 일반 정수 연산기(ALU)로 진행해야 하므로, 스케일 조정 등을 소프트웨어적으로 처리해야 하는 경우가 많다. 그 결과 하드웨어가 부동소수점 연산에 최적화되어 있는 시스템(예: 고성능 서버, PC, 슈퍼컴 등)에서는 오히려 고정소수점 방식이 더 느릴 수도 있다. 반대로 단순한 임베디드 환경(DSP 칩, 마이크로컨트롤러 등)에서는 부동소수점 연산 유닛이 부재하거나, 매우 제한적인 지원만 이루어져서, 고정소수점을 잘 사용하면 하드웨어 자원을 절약하며 높은 효율을 얻을 수 있다.

신호처리, 머신러닝에서의 활용 예

임베디드 신호처리 분야에서는 고정소수점 방식이 DSP 칩과 결합되어 오랫동안 사용되어 왔다. 배정밀도보다 훨씬 적은 메모리를 사용하고, 곱셈·덧셈을 빠르게 수행할 수 있어 이미지·음성·영상 신호처리에 적합한 경우가 많다. 머신러닝 영역에서는 대규모 행렬·벡터 연산이 빈번히 이루어지는데, 모델 학습에 필요한 가중치 갱신과 추론 과정에서 부동소수점 정밀도를 전부 요구하지는 않는 경우가 다수다. 예를 들어 32비트 단정밀도 연산으로도 충분히 우수한 성능을 내며, 16비트(half precision) 또는 8비트, 심지어 1비트(binary neural network) 연산까지 연구되고 있다.

이렇게 수학적으로 필요한 정밀도와 실제 구현상의 이점을 함께 고려해서, 고정소수점에서 부동소수점으로, 또 그 반대 방향으로 적절히 변환하는 기술이 머신러닝, 딥러닝 분야에서도 활발히 연구 중이다. 연산 규모가 거대한 딥러닝 모델에서 혼합 정밀도를 적용하면 메모리 사용량이 획기적으로 줄고, GPU·TPU의 병렬 처리량이 증가하여 학습 속도가 현저히 빨라진다.

---적 분석 회피 및 차후 주제

앞서 살핀 내용을 토대로 고정소수점 방식과 부동소수점 방식은 서로 뚜렷한 장단점을 지니며, 용도와 환경에 따라 선택이 달라진다. 완전히 한쪽으로 치우치기보다 문제 특성에 맞게 혼합하거나 정밀도를 적절히 설정하는 것이 실제 응용에서 흔히 볼 수 있는 접근이다. 부동소수점의 표현 범위와 자동화된 라운딩·정규화 메커니즘은 대규모 과학 계산에 효과적이지만, 정밀도 관리를 철저히 해야 하며, 고정소수점은 제한된 환경에서 안정적인 성능과 예측 가능한 스케일 관리가 장점이지만 범용성은 떨어진다.

Last updated