실수 연산의 한계와 근사적 처리

유한 정밀도와 부동소수점 표현의 본질

컴퓨터에서 사용되는 모든 실수 표현은 유한한 비트(bit)로 이루어져 있다. 이는 엄밀한 실수(real number)의 개념과 대비되는 것으로, 이산(discrete)적인 데이터 구조에 기반을 둔 컴퓨터 연산에서 실수 연산은 본질적으로 근사(approximation)를 내포한다. 예를 들어 10진법으로 0.1과 같은 단순한 수가 2진법 부동소수점(float, double 등) 체계에서 정확히 표현되지 못한다는 사실은 이미 널리 알려져 있다. 따라서 수치계산 알고리즘을 설계하거나 구현할 때, 우리는 항상 부동소수점 표현에 내재된 정밀도 한계와 오차의 전파를 고려해야 한다.

컴퓨터가 내부적으로 채택하고 있는 실수 표현 방식으로는 주로 IEEE 754 표준의 부동소수점 방식이 널리 사용된다. 이 표준에서는 유한한 비트로 구성된 부호(sign), 지수(exponent), 가수(mantissa)를 통해 근사적으로 실수를 표현한다. 가령 배정도(double precision)는 64비트로 구성되며, 부호 1비트, 지수 11비트, 가수 52비트를 사용한다. 이러한 정밀도의 한계로 인해, 실제 계산에서는 $10^{-16}$ 정도의 상대 오차가 발생할 수 있다는 점이 알려져 있다.

정규화(normalized)된 실수 표현에서 가수의 선두 비트(1.xxx...)는 가수부를 최대한 큰 정수로 맞춰 주어진 지수만큼 이동시켜, 유효숫자(significand)를 늘리는 형태로 구현된다. 그러나 매우 작은 크기를 갖는 실수는 비정규화(denormalized) 영역에서 표현되고, 이는 연산 속도와 정밀도 면에서 일반 정규화 구간과 다른 처리를 필요로 한다. 이러한 이유로 실제 계산 과정에서 의도치 않은 정밀도 손실이 발생하기도 한다.

절대 오차와 상대 오차

수치 연산에서 일어나는 오류를 크게 절대 오차(absolute error)와 상대 오차(relative error)로 구분한다. 두 오류의 정의는 다음과 같다.

절대 오차=xapproxxtrue상대 오차=xapproxxtruextrue\begin{align} \text{절대 오차} &= |x_{\text{approx}} - x_{\text{true}}| \\ \text{상대 오차} &= \frac{|x_{\text{approx}} - x_{\text{true}}|}{|x_{\text{true}}|} \end{align}

$x_{\text{true}}$는 실제 참값, $x_{\text{approx}}$는 계산 알고리즘이나 프로그램으로부터 얻어지는 근사값이다. 상대 오차는 참값의 절댓값으로 정규화(normalization)하여 오차 비율을 파악할 수 있게 한다. 값이 매우 작거나 매우 클 때, 어떤 오차가 의미 있는 크기인지 파악하고자 할 때 상대 오차가 유용하다.

머신 엡실론과 반올림

부동소수점 연산에서는 머신 엡실론(machine epsilon)이라는 개념이 중요하다. 머신 엡실론은 $1$과 더했을 때 구분 가능한 가장 작은 양의 수의 차이를 의미한다. IEEE 754 배정도(double precision)에서는 대략 $2^{-52} \approx 2.22\times 10^{-16}$ 정도이다. 이 값은 컴퓨터가 1과 구별할 수 있는 최소 단위이므로, 실제 계산에서 $1 + x = 1$로 처리되어 버리는 $x$의 최소 크기를 나타내기도 한다.

실수 연산의 결과는 부동소수점 표준에 따라 특정 규칙으로 반올림(rounding)된다. 일반적으로 최근접 반올림(round to nearest) 규칙이 사용되지만, 비정규화 수 구간이나 특별한 처리 모드를 통해 다른 반올림 방식을 사용할 수도 있다. 이처럼 부동소수점 반올림은 실제 연산 결과에 필수적인 근사 과정을 더한다.

덧셈과 뺄셈에서의 오차 전파

부동소수점 연산에서 덧셈이나 뺄셈 연산은 가장 대표적인 예로서, 수치적 불안정(numerical instability)을 유발하기 쉽다. 특히 두 값의 절댓값이 서로 크게 다를 때, 혹은 비슷한 크기의 두 값이 뺄셈으로 계산될 때, 유효숫자가 크게 줄어드는 문제가 발생한다. 예를 들어 큰 수에 비해 매우 작은 수를 더하거나 빼는 경우, 작은 수가 정밀도 한계 때문에 결과에 거의 기여하지 못한다. 이런 현상을 대수적(cancelation) 관점에서 보면, 유효숫자 중 상당 부분이 사라져 버리는 소위 ‘치명적 상쇄(catastrophic cancellation)’가 발생할 수 있다.

이와 같은 문제를 직관적으로 설명하기 위해 아래의 간단한 예를 생각할 수 있다. 어떤 큰 값 $A$와 작은 값 $a$가 있을 때, $A + a$의 결과가 $A$와 거의 동일하게 표현될 수 있다. 또, $A$와 $B$가 서로 매우 비슷한 값이라면 $A - B$ 연산 결과는 실제로는 아주 작은 값이어야 함에도 부동소수점 반올림 과정에서 정밀도가 떨어져 크게 왜곡될 수 있다. 이러한 상황을 방지하려면 계산 순서를 재배치하거나, 충분히 큰 정밀도를 확보할 수 있는 알고리즘적 기법을 활용하는 것이 중요하다.

곱셈, 나눗셈, 제곱근 등에서의 근사

덧셈이나 뺄셈 외에도 곱셈, 나눗셈, 제곱근, 지수 및 로그 연산 등에서 다양한 근사적 처리가 발생한다. 예를 들어 곱셈에서 두 수가 매우 크면 오버플로우(overflow)가 일어나고, 매우 작으면 언더플로우(underflow)가 발생할 수 있다. 이를 방지하기 위해 지수부가 자동으로 스케일링(scaling)되지만, 그 과정에서 일정 범위를 벗어나면 계산 결과가 무한대(infinity)나 0으로 표현될 수 있다.

나눗셈 역시 부동소수점 지수 범위 내에서 정확도 확보가 어려울 수 있으며, 특정 알고리즘(예: 뉴턴-랩슨 방법)으로 역수를 구하거나 제곱근을 구하는 과정에서도 일련의 반올림과정이 반복된다. 따라서 알고리즘이 반복 구조를 가진다면, 각 단계에서 발생하는 반올림 오차가 누적될 가능성을 반드시 고려해야 한다.

mermaid를 통한 개념도 예시

spinner

이 도식은 실제 실수(real number)의 연산 과정이 컴퓨터 내에서 어떻게 부동소수점 표준에 맞추어 변환되고, 반올림 과정을 거쳐 근사값으로 처리되는지를 간략히 요약한다. 또한 반복 계산 혹은 복잡한 연산이 누적될 경우 오차가 어떻게 점차 확산될 수 있는지 보여준다.

오차 분석의 필요성과 안정성

수치계산에서 가장 우선되는 관심사는 알고리즘의 안정성(stability)과 오차 분석이다. 동일한 문제라도 표현상의 제약 때문에 구현 방식이나 계산 순서를 어떻게 구성하느냐에 따라 오차 전파 양상이 달라질 수 있다. 예컨대 다음과 같은 형태의 단순화된 예시를 들어볼 수 있다.

f(x)=1cosxxsinxf(x) = \frac{1 - \cos x}{x \sin x}

이 식을 직접 대입 계산할 때, $x$가 매우 작은 영역이라면 $\cos x \approx 1$이므로 분자가 매우 작아지고, 나눗셈 과정에서 부정확한 값이 산출될 가능성이 있다. 이를 개선하기 위해 테일러 전개나 보조 함수를 사용해 수식을 재구성하면 훨씬 안정적으로 계산할 수 있다. 마찬가지로 정수 단위의 큰 상수나 아주 작은 상수와의 조합에 따라 결과값의 상대 오차가 급격히 악화될 수 있기 때문에, 수학적 변형과 알고리즘적 재배치가 필수적이다.

고정소수점과 부동소수점의 차이

수치계산에서 때로는 고정소수점(fixed-point) 연산이 사용되기도 한다. 고정소수점 방식은 소수점 이하 자릿수를 사전에 고정해 두고 정수 연산부만 사용하는 방식이다. 특정 임베디드 시스템이나 DSP(Digital Signal Processing) 분야에서 메모리와 연산 자원의 제약으로 인해 제한된 범위 안에서 고정소수점 연산을 수행하는 경우가 흔하다. 고정소수점 방식에서도 부동소수점처럼 근사 오차가 존재하며, 지원하는 표현 범위가 더욱 제한적이므로 연산 순서와 스케일링이 훨씬 중요하다.

그러나 일반적인 범용 CPU 및 GPU 기반 시스템에서는 압도적으로 부동소수점 연산이 사용된다. 부동소수점이 훨씬 넓은 표현 범위를 제공하며, 동적 스케일링을 자동으로 수행하기 때문이다. 따라서 본 장에서는 주로 부동소수점 연산에서 발생하는 오차의 특성과 그 제어 방법에 대해 다룬다.

IEEE 754 표준과 특별한 값들

컴퓨터에서 부동소수점을 사용해 실수를 표현할 때, IEEE 754 표준은 매우 광범위한 상황을 대응하기 위해 여러 특별한 값들을 정의한다. 양의 무한대(positive infinity), 음의 무한대(negative infinity), 그리고 숫자가 아닌 값(Not a Number, NaN)이 대표적인 예이다. 예컨대 두 매우 큰 수를 곱하다가 오버플로우가 발생하면 그 결과는 일반적으로 무한대 값 중 하나로 처리된다. 0으로 나누었을 때도 비정상적인 결과가 생성되므로, 양의 0에 양수를 나누면 양의 무한대, 음수(혹은 음의 0)에 나누면 음의 무한대가 발생한다. NaN은 계산 과정에서 정의되지 않은 연산, 예컨대 무한대를 무한대로 나누거나, 무한대에서 무한대를 빼는 연산이 일어났을 때 등장한다.

부동소수점 정규화 구간의 아래쪽 경계선 근처에서는 비정규화(denormalized)라는 특별한 수 구간이 존재한다. 일반적인 정규화 구간에서, 가수부는 1.xxx... 형태로 구성되어 유효숫자가 최대화된다. 그러나 비정규화 영역에 속하면 그 선두 비트가 0.xxx... 형태가 되며, 실제 연산 성능이 저하될 수 있다. 현대 프로세서는 대개 비정규화 수 처리에 최적화된 회로를 갖추거나, 빠른 연산을 위해 특정 모드에서 비정규화 수를 자동으로 0에 근사하기도 한다. 그러나 이는 예측 못 한 오차를 추가로 발생시킬 수 있으므로, 민감한 계산을 다룰 때는 주의 깊은 고려가 필요하다.

반올림 모드의 종류와 영향

IEEE 754 표준은 기본적으로 최근접 반올림(round to nearest)을 표준 모드로 채택하지만, 여러 가지 다른 모드도 지원한다. 예를 들어 무조건 올림(round toward +∞), 무조건 내림(round toward -∞), 0으로 절삭(round toward 0) 같은 방식이 가능하다. 다음과 같이 각 모드가 결과에 미치는 영향을 시험할 수 있다.

일반적인 과학 및 공학 계산에서는 대부분 최근접 반올림이 쓰이므로, 다른 모드를 활용하는 경우는 매우 드물다. 다만 금융 연산이나 특정 어셈블리 레벨의 저수준 프로그래밍에서 반올림 모드를 수시로 변경하는 경우가 존재한다. 이처럼 반올림 모드는 결과의 마지막 자릿수를 미세하게 바꾸어 놓을 수 있으며, 반복 계산에선 상당한 누적 오차를 야기할 수도 있으므로 수치해석적 측면에서는 이를 신중하게 다루어야 한다.

가드 비트와 내부 연산 확장

일부 하드웨어 또는 소프트웨어 환경에서는 가드 비트(guard bit)를 두거나 내부적으로 확장된 정밀도를 사용하는 경우가 있다. 예컨대 x86 계열의 일부 FPU(Floating Point Unit)는 내부적으로 80비트 레지스터(확장 정밀도, extended precision)를 사용해 중간 계산을 수행한 뒤, 최종 단계에서 64비트(double precision)로 반올림하여 메모리에 저장한다. 이 경우, 직접 CPU 레지스터에 담겨 있는 중간값과 메모리에 저장된 값이 달라질 수 있어 일종의 “유령 오차(ghost error)”가 발생하기도 한다.

이와 같은 문제는 반복 구조가 있는 알고리즘에서 더욱 두드러진다. 예를 들어 연립방정식을 풀이하는 과정에서 매 반복마다 CPU 내부 레지스터에 담긴 변수를 메모리에 내려쓴 뒤 다시 불러오는 식으로 구현할 경우, 내부 계산값이 늘어났다 줄어드는 과정을 여러 번 겪게 된다. 이러한 현상은 알고리즘을 실제 구현할 때 의도치 않은 오차를 누적시킬 수 있으므로, 중요한 계산이라면 다중 정밀도(multiple precision) 라이브러리를 활용하거나, 내부 확장 정밀도와 메모리 간 자료 형식 변환을 조심스럽게 다루어야 한다.

치명적 상쇄(cancellation) 구체 예시

큰 수끼리의 차 혹은 큰 수에 작은 수를 더하거나 빼는 상황에서 유효숫자가 급격하게 줄어드는 예는 매우 흔하다. 이를 보다 구체적으로 살펴보기 위해 다음과 같은 간단한 예를 제시할 수 있다.

S=(1010+1)1010S = (10^{10} + 1) - 10^{10}

이론적으로는 $S$가 정확히 1이어야 한다. 그러나 $10^{10}$과 1의 크기 차가 매우 커서, 부동소수점 정밀도에서 $10^{10}$을 이진 표현으로 근사할 때 1이 제대로 반영되지 못할 가능성이 있다. 즉 실제 연산 결과가 0으로 근사되는, 혹은 1보다 훨씬 작은 값이 되는 식의 문제가 발생한다. 이를 치명적 상쇄라고 부른다.

비슷하게 $A$와 $B$가 거의 같은 값인데 이 둘의 차 $A - B$만 구하려 할 때, 이론적으로는 매우 작은 값이어야 하지만, 부동소수점 표현 때문에 단 하나의 유효숫자만 남는 식의 대규모 소실(loss of significance)이 일어날 수 있다. 이를 피하기 위해 수식을 재배열하거나, 분모에 자그마한 차가 등장하는 형태라면 보조 항들을 도입해 보완하는 등 다양한 회피 기법이 사용된다.

다음 예시는 Python에서 큰 수와 작은 수를 연산하여 치명적 상쇄를 살펴보는 코드이다.

일부 환경에서는 정확히 1이 출력될 수도 있지만, 일반적으로는 0.0 또는 부동소수점 근사치가 나타난다. 이는 실수가 이진 부동소수점 체계에서 표현되는 본질적 한계를 보여 준다.

반복 구조에서의 오차 누적

수치 해석 알고리즘은 대체로 반복(iteration)을 수반하는 경우가 많다. 뉴턴-랩슨(Newton-Raphson) 방법, 고정점 반복법, 가우스-자이델(Gauss-Seidel) 등 다양한 반복법에서, 각 단계의 연산 결과가 이후 단계의 입력으로 계속 이어진다. 이 과정에서 단계별로 발생한 오차가 누적 또는 증폭될 수 있다.

예컨대 어떤 반복 알고리즘이 아래와 같은 형태를 가진다고 가정하자.

xn+1=g(xn)x_{n+1} = g(x_n)

여기서 $x_n$은 n단계에서의 근사 해, $g$는 어떤 변환 함수를 의미한다. 매 단계마다 $g(x_n)$이 부동소수점 연산을 거치면서 오차가 발생한다. 만약 오차 전파가 상쇄되지 않고 계속해서 증폭되는 구조라면, 알고리즘이 수렴하기 어렵거나 왜곡된 결과를 얻게 된다. 반대로 일정 수준 이상의 오차가 커지지 않도록 안정적인 구조를 갖추거나, 적절한 재배열로 중간 단계에서 발생한 오차를 줄여나가는 방식의 알고리즘이라면 오차가 크지 않게 유지될 수도 있다.

수치 해석 알고리즘을 설계하거나 고안할 때는, 단순히 이론적 수식의 수렴성만 보는 것이 아니라 실제 컴퓨터에서의 부동소수점 오차 누적 현상을 종합적으로 평가해야 한다. 이를 위해 사전적(error analysis) 도구나 후행적(residual analysis) 기법을 함께 동원한다.

조건수와 민감도

함수나 연산 문제의 민감도(sensitivity)를 평가하기 위해 수치 해석에서는 조건수(condition number)라는 개념을 사용한다. 어떤 함수 $f$가 있을 때, 입력값 변화에 비해 출력값이 얼마나 크게 변하는지를 지표로 삼는다. 특히 연립방정식 $A \mathbf{x} = \mathbf{b}$의 해를 구하는 문제에서, 행렬 $A$의 조건수가 매우 크다면 작은 오차가 출력값에 크게 반영되어 수치적으로 불안정한 상황임을 의미한다.

행렬 $A$의 조건수는 보통 2-노름 기준으로 다음과 같이 정의된다.

κ2(A)=A2A12\kappa_2(A) = \|A\|_2 \|A^{-1}\|_2

행렬이 매우 크거나 거의 특이(singular)한 형태라면 $\kappa_2(A)$가 무한대에 가까워진다. 이 경우, $\mathbf{b}$ 혹은 $A$에 아주 미세한 변화가 생기더라도 $\mathbf{x}$가 큰 폭으로 변할 수 있어, 부동소수점 오차가 치명적인 결과를 낳을 가능성이 크다. 이러한 문제를 해결하려면 행렬을 적절히 스케일링하거나, 역조건수( reciprocal condition number )를 활용해 정규화된 형태로 풀어야 한다. 또한 부분 피벗팅(partial pivoting), 정칙화(regularization) 등 다양한 기법이 개발되어 있다.

조건수와 행렬 연산의 안정성

행렬 연산에서 발생하는 부동소수점 오차 전파를 분석할 때, 앞서 언급한 조건수(condition number)가 중요한 역할을 한다. 행렬의 조건수가 클수록, 입력(행렬이나 벡터)에 아주 미세한 변화가 결과 해석에 크게 영향을 미친다. 예를 들어 $A \mathbf{x} = \mathbf{b}$ 형태의 선형계에서 $A$가 역수가 존재하는 정칙 행렬이라면, 이 행렬의 조건수 $\kappa(A)$는 (2-노름 기준으로) 다음과 같이 정의된다.

κ2(A)=A2A12\kappa_2(A) = \|A\|_2 \, \|A^{-1}\|_2

$\kappa_2(A)$가 매우 크면, $A$나 $\mathbf{b}$에 작은 오차가 생겨도 $\mathbf{x}$가 크게 변동되며, 컴퓨터로 구현된 부동소수점 연산에서는 수치적으로 불안정한 계산이 일어날 가능성이 높다. 이 문제를 완화하기 위해서는 행렬을 적절히 스케일링(scaling)하거나, 부분 피벗팅(partial pivoting) 같은 기법으로 계산 순서를 안정적으로 만드는 방법이 있다. LU 분해, QR 분해, SVD(특잉값 분해) 등 고등 연산 기법에서도 내부적으로 조건수나 피벗팅 절차를 고려해 정확도를 높이기도 한다.

행렬이 대칭(symmetric)이고 양의 정부호(positive definite) 형태이거나, 삼각 혹은 대각 우세(diagonally dominant) 구조를 지니면 수치적으로 안정성이 좋아진다. 실제로는 이러한 특성이 없는 일반 행렬에 대해서도, 부분 피벗팅을 적용한 가우스 소거법(Gaussian elimination with partial pivoting, GEPP)을 이용해 안정성을 높인다. 이 과정에서 행 단위로 스왑(swap)을 수행함으로써 연산 중 발생하는 치명적 상쇄를 억제할 수 있다.

반복적 개선(Iterative Refinement)

행렬 연산에서 오차를 줄이기 위한 대표적인 후처리 기법으로 반복적 개선(Iterative Refinement)이 있다. 예컨대 $A \mathbf{x} = \mathbf{b}$를 가우스 소거로 한번 풀어서 얻은 해 $\mathbf{x}_0$가 있을 때, 정확한 해 $\mathbf{x}$와의 차이를 추정해 보정하는 방식으로 작동한다. 구체적인 개념은 다음과 같다.

  1. 근사해 $\mathbf{x}_0$를 이용해 잔차(residual) $\mathbf{r}_0 = \mathbf{b} - A \mathbf{x}_0$를 계산한다.

  2. 이 잔차 방정식 $A \mathbf{e}_0 = \mathbf{r}_0$를 다시 풀어 $\mathbf{e}_0$라는 오차 추정 벡터를 구한다.

  3. $\mathbf{x}_1 = \mathbf{x}_0 + \mathbf{e}_0$로 보정된 해를 얻는다.

이 과정을 반복하여 $\mathbf{x}_n$이 충분히 수렴할 때까지 진행한다. 매 단계에서 $A$가 같으므로, 동일한 분해(LU 등)를 재활용할 수 있다는 이점이 있다. 이 방법은 조건수가 그리 크지 않은 경우, 혹은 소거 중에 발생한 상쇄를 어느 정도 완화하고 싶을 때 유효하게 적용된다. 다만, 조건수가 극도로 큰 경우에는 오차가 반복 과정에서 제대로 수렴하지 않거나 되려 증폭될 수도 있으므로, 실제 적용 시 유의해야 한다.

다중 정밀도(multiple precision) 기법

표준 배정도(64비트)보다 더 높은 정밀도가 필요할 때, 소프트웨어적으로 다중 정밀도를 제공하는 라이브러리를 활용할 수 있다. 대표적인 예로 C++의 GMP( GNU Multiple Precision ), MPFR 라이브러리나 Python의 decimal, mpmath 등이 있다. 이러한 라이브러리는 임의로 많은 자릿수의 부동소수점을 관리할 수 있으므로, 수치적으로 매우 민감하거나 극도의 정밀도를 요구하는 문제에서 적절히 활용될 수 있다.

다중 정밀도 연산은 하드웨어에서 직접 지원되지 않는 경우가 많으므로, 성능 면에서 표준 배정도 부동소수점 연산보다 훨씬 느릴 수 있다. 따라서 모든 문제에 대해 무조건 고정밀 라이브러리를 사용하는 것은 비효율적이며, 문제 특성과 필요한 정밀도 수준에 맞춰 선택적으로 사용하는 것이 일반적이다. 또한 소프트웨어적으로 구현된 고정밀 부동소수점 연산 역시 반올림 과정을 완전히 배제할 수는 없으나, 반올림 단위가 훨씬 작아지므로 결과 오차가 크게 줄어든다는 이점이 있다.

상징적(symbolic) 계산과 수치 해석의 상호보완

상징적 계산(Sympy 등)을 통해 식을 기호대로 취급하는 방식은 이론적인 최적 해나 최소 오차를 보장할 수 있지만, 현실적으로 대규모 문제에서는 매우 느리고 복잡해진다. 반면 수치 해석은 근사 연산을 빠르게 수행하지만, 오차 전파와 반올림이라는 본질적 한계를 안고 있다. 근본적으로 양측은 상호 보완적 관계를 형성할 수 있다. 예컨대 상징적 계산으로 알고리즘을 단순화하거나 전개하여 오차 발생 가능성을 줄이고, 그 결과를 수치 해석 루틴에서 효율적으로 처리하는 식의 결합이 가능하다.

벡터 및 행렬 연산의 실제 예시

다음 예시는 Python에서 부동소수점 오차를 직접 확인할 수 있는 아주 단순한 벡터 연산 실험이다.

행렬 $A$가 대각 요소로 매우 큰 수를, 비대각 요소로 상대적으로 작은 수를 갖고 있기 때문에 수치적으로 불안정한 구조를 이룬다. 이 경우, 부분 피벗팅이 사용되더라도 상쇄 효과로 인해 오차가 생길 수 있다. 더 정밀하게 해석하려면, 같은 문제를 여러 방식으로 풀거나, 다중 정밀도 라이브러리를 적용해 비교하는 시도를 해 볼 수 있다.

실제 문제에서의 오차 평가

실제 응용 문제에서는 입력 데이터가 확정적이라기보다 측정값이거나 추정값인 경우도 많다. 그러한 측면에서, 부동소수점 오차는 측정 오차나 모델 오차와 함께 종합적으로 고려돼야 한다. 기계 공학 시뮬레이션, 금융 모형 평가, 기상 예측 시뮬레이션 등에서 나타나는 입력 불확실성은 수치 해석적 부동소수점 오차와 서로 섞여서 작동한다. 문제의 물리적, 실험적, 혹은 통계적 특성을 반영한 오차 해석이 필요하며, 매우 큰 문제에서는 수치 연산 하나하나의 오차보다는 전체 시스템 모델링이 더 중요한 경우도 존재한다.

그럼에도 불구하고, 특정 상황에서 부동소수점 표현의 한계로 인해 결과가 완전히 붕괴되는 경우가 드물지 않으므로, 수학적으로나 프로그래밍적으로 대비책을 마련하는 것이 바람직하다. 예컨대 상쇄가 예상되는 상황에서는 재배열 기법을 적용하거나, 조건수 개선을 위해 선형대수 알고리즘에서 피벗팅이나 재스케일링을 수행하고, 필요한 경우 다중 정밀도 연산을 병행하는 식으로 접근할 수 있다.

선형대수 문제에서의 전진오차와 후진오차

수치 알고리즘의 안정성을 평가하기 위해, 전진오차(forward error)와 후진오차(backward error)라는 개념이 자주 사용된다. 전진오차는 결과 자체가 참값과 얼마만큼 차이가 나는지를 직접 계산하는 방식이다. 예컨대 $A \mathbf{x} = \mathbf{b}$ 문제에서 실제 해를 $\mathbf{x}{\text{true}}$라 하고, 알고리즘으로부터 얻은 해를 $\mathbf{x}{\text{approx}}$라고 할 때,

xapproxxtrue\|\mathbf{x}_{\text{approx}} - \mathbf{x}_{\text{true}}\|

이 값이 전진오차가 된다. 그러나 실제 계산 과정에서 $\mathbf{x}_{\text{true}}$를 알고 있는 경우는 많지 않으며, 수치적으로 불안정한 상황에선 오차가 어떻게, 왜 발생했는지를 정확히 추적하기 어렵다.

후진오차는 조금 다른 방식으로 접근한다. 알고리즘이 어떤 $\mathbf{x}_{\text{approx}}$를 결과로 내놓았다고 할 때, 이 해가 정확히 $A + \Delta A$와 $\mathbf{b} + \Delta \mathbf{b}$에 대한 해라고 보면 어떨까 하는 관점이다. 즉 실제로는 약간 달라진(perturbed) 문제

(A+ΔA)xapprox=b+Δb(A + \Delta A) \, \mathbf{x}_{\text{approx}} = \mathbf{b} + \Delta \mathbf{b}

에 대한 정확한 해일 수 있다. 그렇다면 $\Delta A$나 $\Delta \mathbf{b}$의 크기를 원래 $A$, $\mathbf{b}$에 비해 얼마나 작게 만들 수 있는지, 그 상대적 크기를 측정하는 방식으로 알고리즘의 후진오차를 정의한다. 후진오차가 작은 알고리즘이라면, “조금 다른 문제”에는 정확히 맞는 해를 구했다고 볼 수 있으므로, 결과를 신뢰할 만하다고 평가한다. 이와 달리 전진오차가 작더라도 해당 결과가 실제로 완전히 다른 문제를 푼 것일 수도 있으므로, 수치적 안정성 측면에서 후진오차를 더 선호하기도 한다.

안정 알고리즘(stable algorithm)과 불안정 알고리즘(unstable algorithm)

수치적으로 안정적인(stable) 알고리즘은 그 방법을 통해 계산된 결과가 후진오차 관점에서 작게 유지되는 특징을 갖는다. 즉, 알고리즘 수행 중에 부동소수점 반올림과 같은 불가피한 오차가 섞여 들어오긴 해도, 결과적으로 “약간만 달라진” 문제를 정확히 해결한 꼴이 되도록 제어한다. 대표적으로 가우스 소거법에 부분 피벗팅을 적용하는 GEPP(Gaussian Elimination with Partial Pivoting)는 여러 고전적 분석에서 안정 알고리즘으로 분류된다.

그러나 어떤 알고리즘은 문제 구조가 조금만 달라져도 오차가 크게 증폭되거나, 부동소수점 표현의 상쇄 문제를 전혀 회피하지 못해 결과가 극도로 왜곡될 수 있다. 예컨대 다항식 근 찾기(polynomial root finding)에서 직관적으로 호른 분해(Horner’s method)를 쓰지 않고 고차항부터 단순히 순차 계산을 진행하면, 특히 큰 계수와 작은 계수가 섞여 있는 경우 치명적 상쇄가 발생하기 쉽다. 이처럼 불안정한 접근은 같은 문제라도 다른 안정 알고리즘과 비교했을 때 훨씬 부정확한 결과를 내놓게 된다.

다항식 계산의 안정성과 호른 분해

다항식

p(x)=anxn+an1xn1++a1x+a0p(x) = a_n x^n + a_{n-1} x^{n-1} + \dots + a_1 x + a_0

을 계산할 때, 가장 직관적인 방법은 $x^k$를 차례대로 구해 계수와 곱한 뒤 더하는 방식이다. 그러나 $x^k$가 매우 큰 수 또는 매우 작은 수가 되어 계수들과 함께 상쇄가 발생할 수 있다. 이를 방지하기 위해 호른 분해(Horner’s method)를 사용한다. 호른 분해는 다음과 같은 형태로 다항식을 재구성한다.

p(x)=an(x(x((x+a1an)+a2an)+))+a0anp(x) = a_n \Bigl( x \bigl( x(\dots (x + \frac{a_1}{a_n}) + \frac{a_2}{a_n}) + \dots \bigr) \Bigr) + \frac{a_0}{a_n}

이 방법을 구체적으로 전개하면, 곱셈-덧셈 과정을 일렬로 배열하여 상쇄가 덜 일어나는 순서로 계산을 수행하게 된다. 결과적으로 부동소수점 반올림 오차가 누적되는 양이 감소하여, 같은 정밀도에서도 더 정확한 결과를 낼 수 있다. 특히 $|x|$가 큰 경우에도 상대적으로 안정적인 결과를 기대할 수 있다는 장점이 있다.

고차 다항식 근 찾기와 안정성 문제

다항식의 근을 구하는 문제(예: $p(x) = 0$)는 자연 과학과 공학 전반에서 흔히 등장한다. 그러나 차수가 높아질수록 근의 분포가 복잡해지고, 작은 계수 변화나 부동소수점 오차로 인해 해의 위치가 크게 흔들릴 가능성이 있다. 게다가 일부 알고리즘은 루프 내에서 작은 차이를 반복적으로 계산하거나, 결과가 여러 근 중 하나로 강제 수렴해 버리는 현상이 나타나기도 한다.

예컨대 전통적인 배제법(bracketing method) 계열은 구간을 좁혀 가며 근을 찾기에 비교적 안정적이지만, 수렴 속도가 빠르지 않을 수 있다. 뉴턴-랩슨(Newton-Raphson) 방법은 수렴 속도가 빠른 장점을 갖지만, 초기치 선택이나 함수의 형태에 따라 발산하거나 엉뚱한 근으로 유도될 위험이 있고, 미분값이 작은 지점에서 치명적 상쇄를 겪을 수 있다. 실전에서는 이 둘을 절충하거나, 여러 알고리즘을 혼합하여 활용하는 방안이 모색된다.

고차 방정식에서 라고에따 기법(Ragouet’s method) 등

고차 다항식의 근을 실용적으로 구하기 위해, 다양한 특수 기법들이 연구되어 왔다. 예컨대 대칭 다항식으로 변환하거나, 특정 정리(예: 루피니, 대수적 분해)를 활용해 차수를 낮추는 절차를 거치기도 한다. 또한 라고에따 기법(Ragouet’s method), 버치-스왈츠(Birch-Swatz) 알고리즘 등 실무에서는 좀 더 드문 방식들이 존재하지만, 목표는 모두 부동소수점 상쇄와 오버플로우·언더플로우 위험을 최소화하여 정확한 근을 찾는 데 있다.

특정 문제에서 근들이 매우 가까이 밀집(clustering)되어 있으면, 각 근을 구분해내는 데에도 고난도의 정밀도가 요구된다. 이를 해결하기 위해 분할-정제(divide and conquer) 방식으로 근들을 하나씩 골라내거나, 안정적인 근 추적(root tracking) 알고리즘을 적용하는 시도가 이뤄진다.

다항 근사와 실험 데이터의 피팅

실험 데이터에 대한 다항식 피팅(polynomial fitting)도 숫자 오차를 심각하게 유발할 수 있는 주제다. 예컨대 $n$차 다항식으로 $m$개의 데이터를 최소제곱법(Least Squares Method)으로 피팅하려 할 때, 정상적인 문제 설정이라도 행렬의 조건수가 매우 커질 수 있다. 베이더-반더몽드(Vandermonde) 행렬이 대표적 예다. 이 행렬은 데이터 지점들이 일정 간격으로 고르게 분포되어 있을 경우, 원소가 $x_i^j$ 형태로 구성되어 비약적으로 큰 수나 매우 작은 수가 발생하기 쉽고, 이에 따라 수치 오차가 폭발적으로 늘어난다.

따라서 고차 다항식 대신 스플라인(spline)이나 직교다항식(orthogonal polynomial)을 사용하거나, 디스크리트 푸리에 변환(DFT) 같은 기법으로 문제를 다른 형태로 전환하는 접근이 권장되기도 한다. 실제로 직교다항식 기반의 최소제곱 접근은 베이더-반더몽드 행렬 대비 훨씬 안정적인 조건수를 확보할 수 있다.

편미분방정식(PDE) 해석과 그리드 분할

수치해석의 대표적 활용 영역 중 하나인 편미분방정식(PDE) 문제에서는, 공간과 시간을 일정한 간격으로 디스크리트(discretize)하게 나누어 근사 해를 구한다. 이 때, 그리드 간격이 아주 미세해질수록 계산량이 급격히 증가할 뿐 아니라, 부동소수점 연산 횟수도 방대해진다. 그 결과 문제의 안정성(stability)이나 수렴성(convergence)을 이론적으로 분석하더라도, 실제 연산 과정에서 오차가 누적되고 반올림 문제가 발생할 수 있다.

예를 들어 열전도방정식(heat equation)을 유한차분법(Finite Difference Method)으로 풀 때, 시간 간격 $\Delta t$와 공간 간격 $\Delta x$의 비율이 특정 조건(Courant-Friedrichs-Lewy 조건 등)을 충족해야만 해가 수렴성을 보장할 수 있다. 그러나 실제로는 $\Delta t$나 $\Delta x$가 아주 작아졌을 때, 계산 스텝이 여러 번 반복되며, 상쇄 오차나 반올림 오차가 누적되어 결과가 편향될 수 있다. 수치 점도의 개념(numerical viscosity)처럼, 오차를 일종의 모사로 인식하여 그 영향력을 평가하고자 하는 접근도 있다.

확률적(몬테카를로) 계산에서의 부동소수점 이슈

몬테카를로 시뮬레이션이나 확률적 방법을 사용하는 계산에서도 부동소수점 한계는 여전히 중요하다. 일반적으로 랜덤 샘플링을 많이 해서 통계량을 추정하는 기법은, 개별 연산 정확도가 살짝 떨어지더라도 통계적으로 평균화되므로 결과가 크게 왜곡되지 않을 것이라 보는 경우가 많다. 실제로도 어느 정도는 맞는 가정이지만, 매우 극단적인 확률 사건을 다루거나, 지수적 스케일로 값이 커지거나 작아지는 상황에서는 여전히 부동소수점 표현에서 심각한 오차가 발생할 수 있다.

예컨대 금융 공학에서 몬테카를로 방법으로 옵션 가격을 시뮬레이션할 때, 만기 시점의 가격 분포가 가우시안 꼬리를 넘어서 멀리 뻗어나가는 경우가 있다. 이 때 특정 경계 조건을 평가하면서 지수 함수(exp)나 로그 함수(log)를 많이 쓰게 되면, 오버플로우·언더플로우가 빈번하게 일어날 수 있으며, 이로 인해 시뮬레이션 결과가 발산하거나 0으로 몰릴 우려가 있다. 적절한 로그 변환 또는 스케일링 기법을 수반해, 중간 연산에서 발생할 수 있는 분포의 편차를 제어해야 한다.

관측값 오차와 수치 오차의 통합 처리

현실 세계에서 입력으로 제공되는 데이터는 이미 측정 오차나 잡음(noise)을 포함하고 있는 경우가 많다. 계측기의 정밀도, 실험 환경의 불확실성, 자연계의 무작위성 등 다양한 요인으로 인해, 데이터 자체가 결코 ‘진짜(real)’ 값을 갖지 않는다. 따라서 모델링이나 해석 과정에서, 이러한 측정 오차와 컴퓨터 부동소수점 오차를 동시에 다뤄야 한다.

수치해석 관점에서는, 측정 오차가 워낙 커서 부동소수점 반올림 오차나 상쇄가 상대적으로 미미한 문제로 여겨질 수 있다. 반대로 매우 정밀한 과학기기나 특별히 세밀한 계산을 요구하는 문제(예: 위성 궤도 결정, 초음파 영상 처리 등)에서는, 부동소수점 오차가 측정 오차 수준과 비슷하거나 오히려 더 커지는 상황도 발생한다. 즉 문제의 본질과 요구 정밀도에 따라, 어느 영역을 더 중점적으로 고려해야 할지가 달라진다.

연구 동향과 지속적인 개선

과거에는 부동소수점 연산의 정밀도(32비트, 64비트, 80비트 등)와 속도, 그리고 소프트웨어 알고리즘의 개발이 주로 관심사였다. 최근에는 GPU(Graphic Processing Unit)를 이용한 병렬 연산이나, 혼합 정밀도(mixed-precision) 기법이 활발히 연구되고 있다. 예컨대 특정 단계에서는 단정도(single precision, 32비트)로 대량의 연산을 수행하고, 필요한 핵심 단계에서만 배정도(double precision, 64비트) 또는 다중 정밀도를 사용함으로써 계산 시간과 에너지 효율을 높이면서도 일정 수준 이상의 정확도를 유지하려는 시도가 대표적이다.

하드웨어 측면에서도 뉴로모픽(neuromorphic) 칩이나 양자(quantum) 계산에 대한 연구가 진전되면서, 전통적인 이진 부동소수점 체계를 벗어나려는 시도도 나타나고 있다. 그러나 당분간은 IEEE 754 형식을 기반으로 한 표준 부동소수점 연산이 가장 널리 쓰일 것이므로, 이러한 환경에서의 수치적 안정성과 오차 제어는 핵심적인 연구·실무 과제로 남아 있다.

Last updated