Chapter 02

조건수와 안정성

계산 결과가 틀렸을 때 책임은 둘 중 하나에 있습니다. 문제가 원래 예민해서 입력의 작은 오차가 답을 크게 흔드는 경우, 아니면 알고리즘이 계산 도중에 필요 없는 오차를 키운 경우입니다. 앞의 것은 어떤 알고리즘으로도 고칠 수 없고, 뒤의 것은 식을 바꾸면 고칠 수 있습니다. 이 장은 둘을 재는 두 개의 자를 만듭니다. 문제의 민감도는 조건수 κ\kappa로, 알고리즘의 품질은 후방 오차로 잽니다. 그리고 수치해석에서 가장 많이 쓰는 어림식 하나로 둘을 묶습니다. 전방 오차 ≲ 조건수 × 후방 오차. 1장의 반올림 단위 uu가 "후방 오차"의 단위가 되고, 이 장의 조건수가 그 오차가 답에서 몇 배로 보일지를 알려 줍니다.

이 수식이 없었다면믿고 쓰는 수치 라이브러리

1940년대 말 전자계산기가 처음 큰 선형계를 풀기 시작했을 때, 많은 사람이 비관했습니다. 소거 한 번마다 반올림 오차가 생기고, 미지수가 수십 개면 연산이 수만 번이니 오차가 걷잡을 수 없이 쌓일 것이라는 계산이었습니다. 영국 국립물리연구소에서 튜링(Alan Turing)과 함께 일한 윌킨슨(James H. Wilkinson)은 질문을 뒤집었습니다. "계산된 답은 참값과 얼마나 다른가"를 묻는 대신 "계산된 답은 어떤 조금 다른 문제의 정확한 답인가"를 물은 것입니다. 이것이 후방 오차 분석입니다. 그는 부분 피벗팅 가우스 소거 같은 알고리즘이 원래 행렬을 반올림 단위 수준으로만 바꾼 문제를 정확히 푼다는 것을 보였고, 이 업적으로 1970년 튜링상을 받았습니다.

이 관점 덕분에 오차 분석이 두 조각으로 나뉩니다. 알고리즘이 후방 안정한가(라이브러리 설계자의 일), 문제의 조건수가 얼마인가(사용자가 알아야 할 일). LAPACK 같은 선형대수 라이브러리가 해와 함께 조건수 추정값을 돌려주는 이유가 여기에 있습니다. 시뮬레이션, 회로 해석, 기계 학습의 모든 계산이 이 분업 위에서 신뢰를 얻습니다.

답이 틀렸다→문제 탓인가, 알고리즘 탓인가→전방 오차 ≲ κ × 후방 오차→믿을 수 있는 계산

문제의 민감도와 알고리즘의 품질

4자리밖에 맞지 않는 두 계산

(가) 센서가 x=1.0001x = 1.0001을 상대 오차 10−810^{-8}으로 측정했습니다. 이 값으로 ln⁡x\ln x를 계산했더니 답의 상대 오차가 10−410^{-4}입니다. (나) x=10−10x = 10^{-10}은 정확히 주어졌는데, Math.log(1 + x)로 ln⁡(1+x)\ln(1 + x)를 계산했더니 상대 오차가 8.3×10−88.3\times10^{-8}입니다. 두 경우 모두 기대보다 많은 자릿수를 잃었습니다. 그런데 하나는 어떤 방법으로도 더 나아지지 않고, 다른 하나는 함수 하나만 바꾸면(Math.log1p(x)) 16자리가 다 맞습니다. 어느 쪽이 어느 쪽일까요?

(가)에서는 계산이 완벽해도 소용이 없습니다. 입력 xx 자체가 10−810^{-8}만큼 불확실하고, ln⁡\ln이 1 근처에서 입력의 상대 변화를 1만 배로 키우기 때문입니다. 이것은 문제의 성질입니다. (나)에서는 문제 x↦ln⁡(1+x)x \mapsto \ln(1 + x)가 전혀 예민하지 않습니다. 입력이 10−810^{-8} 바뀌면 답도 약 10−810^{-8} 바뀝니다. 오차는 계산 순서 탓입니다. 1+x1 + x를 먼저 반올림하는 순간 xx의 아래 자리가 u≈10−16u \approx 10^{-16}만큼 잘려 나가는데, 그 잘림은 xx에 견주면 10−16/10−10=10−610^{-16}/10^{-10} = 10^{-6} 정도입니다. 이것은 알고리즘의 성질입니다.

정의 2.1 (문제와 알고리즘) 수치 계산의 문제는 입력 xx에서 정확한 출력 y=f(x)y = f(x)로 가는 사상 ff다. 알고리즘은 부동소수점 연산의 유한한 나열로 ff의 근삿값 y^=f^(x)\hat y = \hat f(x)를 만드는 절차다. 같은 ff를 계산하는 알고리즘은 여러 가지다. 문제가 입력의 작은 교란에 얼마나 민감한지를 문제의 조건(conditioning)이라 하고, 알고리즘이 반올림 오차를 얼마나 억제하는지를 알고리즘의 수치적 안정성(numerical stability)이라 한다.
문제 f의 조건 (조건수 κ) 알고리즘과 무관한, 문제 자체의 성질 x ± δ f f(x) ± κδ 입력의 상대 오차가 κ배로 퍼진다 알고리즘 f̂의 안정성 (후방 오차) 같은 f라도 계산 순서에 따라 다르다 x x̂ f̂ (부동소수점) f (정확) ŷ ŷ = f(x̂)인 x̂가 x에 가까우면 안정
그림 2-1. 결과 오차의 두 출처. 왼쪽: 문제는 입력의 불확실성(측정 오차, 저장할 때의 반올림 uu)을 조건수 κ\kappa배로 키운다. 오른쪽: 알고리즘의 결과 y^\hat y를 "조금 다른 입력 x^\hat x에 대한 정확한 답"으로 볼 때, x^\hat x가 xx에서 uu 정도만 떨어져 있으면 알고리즘은 할 일을 다 한 것이다. 둘을 합치면 결과의 상대 오차는 약 κ×(x^\kappa \times (\hat x의 상대 거리))다(정리 2.9).

이 구분은 실무에서 질문을 바꿔 줍니다. 결과가 이상하면 먼저 "조건수가 얼마인가"를 묻습니다. 조건수가 101210^{12}이면 배정밀도의 16자리 중 12자리는 원래 믿을 수 없는 것이니, 알고리즘을 고칠 것이 아니라 문제를 바꿔야 합니다(측정을 더 정확히, 모델을 다르게, 변수를 바꿔서). 조건수가 작은데 결과가 나쁘면 그때는 알고리즘을 바꿉니다. MathBook의 조건수 맛보기와 LinearBook의 행렬 조건수를 여기서 일반적인 틀로 다시 세웁니다.

상대 조건수: 입력 오차가 몇 배로 퍼지는가

사인 값이 6자리를 잃는 곳

x=3.14159x = 3.14159에서 sin⁡x\sin x를 계산합니다. 입력의 마지막 자리 하나가 바뀌면(상대 변화 3×10−63\times10^{-6}) 답은 얼마나 바뀔까요? sin⁡x≈π−x=2.65×10−6\sin x \approx \pi - x = 2.65\times10^{-6}이므로 xx가 10−510^{-5}만 움직여도 답이 네 배쯤 달라집니다. 반면 x=1x = 1에서는 같은 입력 변화가 답을 거의 같은 비율로만 바꿉니다. 같은 함수인데 어디서 계산하느냐에 따라 예민함이 백만 배 다릅니다. 이것을 하나의 수로 재고 싶습니다.

입력이 xx에서 x+Δxx + \Delta x로 바뀌면 출력은 f(x+Δx)−f(x)≈f′(x) Δxf(x + \Delta x) - f(x) \approx f'(x)\,\Delta x만큼 바뀝니다(1차 테일러 근사, CalculusBook의 미분과 오차 전파). 양변을 f(x)f(x)로 나누고 오른쪽에 x/xx/x를 끼워 넣으면 상대 변화끼리의 관계가 나옵니다.

f(x+Δx)−f(x)f(x)≈xf′(x)f(x)⏟증폭 인자⋅Δxx\frac{f(x + \Delta x) - f(x)}{f(x)} \approx \underbrace{\frac{x f'(x)}{f(x)}}_{\text{증폭 인자}}\cdot\frac{\Delta x}{x}
정의 2.2 (상대 조건수) 미분 가능한 ff와 x≠0x \ne 0, f(x)≠0f(x) \ne 0에 대해
κf(x)=∣xf′(x)f(x)∣\kappa_f(x) = \left|\frac{x f'(x)}{f(x)}\right|
를 xx에서 ff의 상대 조건수(relative condition number)라 한다. ∣f′(x)∣|f'(x)|는 절대 조건수다. κ\kappa가 1 정도면 조건이 좋다(well-conditioned), 매우 크면 조건이 나쁘다(ill-conditioned)고 한다.
정리 2.3 (조건수와 출력 오차) ff가 xx 근처에서 두 번 연속 미분 가능하고 x^=x(1+δ)\hat x = x(1 + \delta)이면
∣f(x^)−f(x)f(x)∣=κf(x) ∣δ∣+O(δ2).\left|\frac{f(\hat x) - f(x)}{f(x)}\right| = \kappa_f(x)\,|\delta| + O(\delta^2).

증명. 테일러 정리로 f(x^)=f(x)+f′(x)xδ+12f′′(ξ)x2δ2f(\hat x) = f(x) + f'(x)x\delta + \tfrac12 f''(\xi)x^2\delta^2. f(x)f(x)로 나누면 첫 항이 (xf′(x)/f(x))δ\bigl(x f'(x)/f(x)\bigr)\delta, 나머지가 O(δ2)O(\delta^2)이다. ∎

실용적인 읽기: 입력이 uu 정도 정확하면(저장만 해도 그렇다) 출력의 상대 오차는 약 κu\kappa u이고, 10진 자릿수로 log⁡10κ\log_{10}\kappa자리를 잃는다.

함수 f(x)κf(x)조건이 나쁜 곳예: κ 값
xa (√x는 a = 1/2)|a|없음(어디서나 |a|)√x: 0.5
ex|x||x|가 클 때x = 100: 100
ln x1/|ln x|x ≈ 1x = 1.0001: 1.0×10⁴
sin x|x cot x|x ≈ kπ (k ≠ 0)x = 3.14159: 1.18×10⁶
cos x|x tan x|x ≈ π/2 + kπx = 1.5707: 1.63×10⁴
tan x|2x / sin 2x|x ≈ kπ/2 (k ≠ 0)x = 1.57: 1.97×10³
x − 1|x / (x − 1)|x ≈ 1x = 1.0001: 1.0×10⁴
1/x1없음1

표에서 몇 가지 규칙이 보입니다. 곱셈형 연산(거듭제곱, 역수)은 언제나 조건이 좋습니다. 조건이 나빠지는 곳은 거의 언제나 출력이 0에 가까워지는 곳(ln⁡1=0\ln 1 = 0, sin⁡π=0\sin\pi = 0, cos⁡(π/2)=0\cos(\pi/2) = 0)이거나 출력이 무한대로 가는 곳(tan⁡\tan의 극점)입니다. 0 근처에서 상대 오차는 작은 절대 변화에도 크게 바뀌기 때문입니다. 지수 함수는 특별합니다. exe^x의 조건수 ∣x∣|x|는 x=700x = 700이면 700이라, 입력의 uu가 출력에서는 700u≈8×10−14700u \approx 8\times10^{-14}가 됩니다. 공학의 아레니우스 식 e−E/RTe^{-E/RT}처럼 지수가 큰 모델이 온도 측정 오차에 예민한 이유입니다.

예제 2.1 (조건수를 실험으로 재기) 문제 상자 (가)의 ln⁡x\ln x, x=1.0001x = 1.0001과 tan⁡x\tan x, x=1.57x = 1.57의 조건수를 공식과 교란 실험으로 비교하라.
  1. 공식: κln⁡(1.0001)=1/ln⁡(1.0001)=10000.50\kappa_{\ln}(1.0001) = 1/\ln(1.0001) = 10000.50. κtan⁡(1.57)=∣2⋅1.57/sin⁡3.14∣=1971.55\kappa_{\tan}(1.57) = |2\cdot1.57/\sin 3.14| = 1971.55.
  2. 실험: xx에 [−10−8,10−8][-10^{-8}, 10^{-8}]의 무작위 상대 교란 δ\delta를 1000번 주고 ∣Δy/y∣/∣δ∣|\Delta y/y|/|\delta|의 평균을 구하면 ln⁡\ln은 10000.5010000.50, tan⁡\tan은 1971.551971.55다. 정리 2.3 그대로다.
  3. 교란이 크면 1차 근사가 깨진다. tan⁡(1.57)=1255.766\tan(1.57) = 1255.766, tan⁡(1.57×(1+10−6))=1258.246\tan(1.57\times(1 + 10^{-6})) = 1258.246으로 상대 변화는 1.98×10−31.98\times10^{-3}이다. κδ=1.97×10−3\kappa\delta = 1.97\times10^{-3}과 거의 같지만, 교란을 10−310^{-3}으로 키우면 xx가 극점 π/2=1.5708\pi/2 = 1.5708을 넘어가 부호까지 바뀐다.
SIMULATOR

입력 교란 → 출력 퍼짐: 조건수 실험

κ(x) (로그 축) · 세로선 = 현재 x
입력 상대 교란 δ → 출력 상대 변화
교란 크기 |δ| ≤
교란 실험 한 번예측 기울기 ±κ
x · f(x)—
이론 κ = |x f′/f|—
실험 max |Δy/y| / |δ|—
잃는 자릿수 log₁₀κ—
해볼 것: ① ln x에서 x를 1 쪽으로 끌면 아래 그림의 점들이 가파른 직선 위에 늘어섭니다. 기울기가 곧 κ입니다. ② sin x를 π 근처로, tan x를 π/2 근처로 옮겨 κ가 폭발하는 것을 보세요. ③ 교란을 10⁻³으로 키우면 조건이 나쁜 곳에서 점들이 직선을 벗어나 휘어집니다. 정리 2.3의 O(δ²) 항이 보이는 것입니다. 모델: x̂ = x(1 + δ), δ는 균등 분포, f는 배정밀도 라이브러리 함수.

함수별 조건수: 빼기, 여러 변수, 방정식의 근

입력이 여러 개인 문제도 같은 생각으로 다룹니다. 각 입력이 상대적으로 δi\delta_i만큼 흔들릴 때 출력의 상대 변화는 1차까지 ∑i(xi ∂f/∂xi)/f⋅δi\sum_i (x_i\,\partial f/\partial x_i)/f \cdot \delta_i입니다.

정의 2.4 (성분별 상대 조건수) f(x1,…,xn)f(x_1, \dots, x_n)과 ∣δi∣≤δ|\delta_i| \le \delta인 교란 x^i=xi(1+δi)\hat x_i = x_i(1 + \delta_i)에 대해 ∣f(x^)−f(x)∣/∣f(x)∣≤κ δ+O(δ2)|f(\hat x) - f(x)|/|f(x)| \le \kappa\,\delta + O(\delta^2)을 만족하는 가장 작은 수는
κf(x)=∑i=1n∣xi∣ ∣∂f/∂xi∣∣f(x)∣.\kappa_f(x) = \frac{\sum_{i=1}^{n} |x_i|\,|\partial f/\partial x_i|}{|f(x)|}.
연산κ (정의 2.4)읽는 법
x + y (같은 부호)(|x| + |y|)/|x + y| = 1양수끼리의 덧셈은 언제나 안전
x − y (같은 부호)(|x| + |y|)/|x − y|x ≈ y이면 무한히 커진다: 자릿수 상쇄
x · y, x / y2두 입력의 상대 오차가 더해질 뿐
√(x² + y²)1조건이 좋다(1장의 빗변 문제는 중간값의 범위 문제였다)
xy|y| + |y ln x|지수가 크거나 밑이 0·∞에서 멀 때

뺄셈의 조건수 (∣x∣+∣y∣)/∣x−y∣(|x| + |y|)/|x - y|는 1장 정리 1.9의 증폭 인자 그대로입니다. 1장에서는 "뺄셈이 오차를 키운다"고 했지만, 이제 더 정확히 말할 수 있습니다. 뺄셈 연산은 정확하거나 거의 정확합니다. 키우는 것은 x≈yx \approx y에서 조건이 나쁜 뺄셈이라는 문제이고, 그 문제에 들어가는 입력이 이미 오차를 품고 있을 때 피해가 드러납니다. 예를 들어 1.0001−11.0001 - 1의 조건수는 2.0001/0.0001≈2.0×1042.0001/0.0001 \approx 2.0\times10^4입니다.

아래 지도는 입력이 둘인 문제의 조건수를 평면 전체에 색으로 칠합니다. 조건이 나쁜 곳은 대개 곡선 하나를 따라 몰려 있습니다. 뺄셈은 대각선 x=yx = y, 이차방정식의 근은 판별식이 0인 포물선 위입니다.

SIMULATOR

조건수 지도: 입력 평면에서 예민한 곳 찾기

지도를 클릭·드래그해 입력 고르기
κ ≈ 1κ ≈ 10⁶κ ≥ 10¹²정의 안 됨(복소근 등)
입력—
출력 f—
조건수 κ—
실험 (교란 10⁻⁹, 200번)—
해볼 것: ① x − y에서 대각선 가까이 가면 κ가 10⁶, 10⁹로 뛰고, 대각선에서 멀어지면 1에 가까워집니다. 반대 부호 영역(제2·4사분면)은 사실 덧셈이라 κ ≤ 1입니다. ② x · y는 어디서나 2라 지도가 한 색입니다. ③ 이차방정식은 판별식 b² − 4c = 0인 포물선 위에서 κ가 무한대입니다. 중근은 조건이 나쁩니다(정리 2.6). 모델: 정의 2.4의 성분별 조건수, 실험은 각 입력에 독립 균등 상대 교란.

방정식의 근은 얼마나 예민한가

3장에서 푸는 문제 f(x)=0f(x) = 0도 하나의 문제입니다. 입력은 함수 ff(실제로는 그 계수나 매개변수), 출력은 근 rr입니다. ff를 f+εgf + \varepsilon g로 조금 바꾸면 근이 r+Δrr + \Delta r로 움직이고, 0=f(r+Δr)+εg(r+Δr)≈f′(r)Δr+εg(r)0 = f(r + \Delta r) + \varepsilon g(r + \Delta r) \approx f'(r)\Delta r + \varepsilon g(r)에서 Δr\Delta r을 구할 수 있습니다.

정리 2.5 (단순근의 조건수) f(r)=0f(r) = 0, f′(r)≠0f'(r) \ne 0이면 f+εgf + \varepsilon g의 근 r(ε)r(\varepsilon)은
r(ε)=r−ε g(r)f′(r)+O(ε2).r(\varepsilon) = r - \varepsilon\,\frac{g(r)}{f'(r)} + O(\varepsilon^2).
특히 다항식 p(x)=∑kakxkp(x) = \sum_k a_k x^k의 계수를 ak(1+εk)a_k(1 + \varepsilon_k), ∣εk∣≤ε|\varepsilon_k| \le \varepsilon로 바꾸면
∣Δr∣∣r∣≲κr ε,κr=∑k∣ak∣ ∣r∣k∣r∣ ∣p′(r)∣.\frac{|\Delta r|}{|r|} \lesssim \kappa_r\,\varepsilon, \qquad \kappa_r = \frac{\sum_k |a_k|\,|r|^k}{|r|\,|p'(r)|}.

증명의 핵심. 음함수 정리로 r(ε)r(\varepsilon)이 매끄럽고 r′(0)=−g(r)/f′(r)r'(0) = -g(r)/f'(r)이다. 다항식에서는 εg(x)=∑kεkakxk\varepsilon g(x) = \sum_k \varepsilon_k a_k x^k이고 ∣g(r)∣≤ε∑∣ak∣∣r∣k|g(r)| \le \varepsilon\sum|a_k||r|^k이다. ∎

분모 f′(r)f'(r)이 핵심입니다. 근에서 그래프가 납작하면, 곧 f′(r)f'(r)이 작으면 ff를 위아래로 조금만 흔들어도 그래프가 0을 지나는 자리가 크게 미끄러집니다. 3장 멈춤 조건에서 본 "불확실 구간"이 바로 이것입니다. f′(r)=0f'(r) = 0인 중근에서는 정리 2.5가 아예 쓸모없고, 더 나쁜 일이 일어납니다.

정리 2.6 (중근의 민감도) rr이 ff의 mm중근(f(r)=⋯=f(m−1)(r)=0≠f(m)(r)f(r) = \dots = f^{(m-1)}(r) = 0 \ne f^{(m)}(r))이고 g(r)≠0g(r) \ne 0이면, f+εgf + \varepsilon g의 근은 rr 근처에서
∣Δr∣≈∣m! ε g(r)f(m)(r)∣1/m|\Delta r| \approx \left|\frac{m!\,\varepsilon\,g(r)}{f^{(m)}(r)}\right|^{1/m}
만큼 움직인다. 교란이 ε\varepsilon이면 근의 오차는 ε1/m\varepsilon^{1/m}에 비례한다. 조건수(비)가 유한하지 않다.
예제 2.2 (이중근이 8자리를 잃는다) f(x)=(x−1)2f(x) = (x - 1)^2을 f(x)−εf(x) - \varepsilon으로 바꾸면 근은 1±ε1 \pm \sqrt\varepsilon이다.
  1. ε=10−16\varepsilon = 10^{-16}(배정밀도로 ff를 계산할 때 생기는 크기)이면 근이 10−810^{-8} 움직인다. 16자리 정확한 함숫값으로 8자리 근밖에 얻지 못한다.
  2. ε<0\varepsilon \lt 0이면 실근이 사라지고 1±i∣ε∣1 \pm i\sqrt{|\varepsilon|}가 된다. 아래 지도의 포물선 바깥(회색)으로 나가는 것이다.
  3. 3중근이면 ε1/3≈5×10−6\varepsilon^{1/3} \approx 5\times10^{-6}, 곧 5자리 남짓만 남는다. 3장 예제 3.7의 7중근에서는 ε1/7\varepsilon^{1/7}이 되어 불확실 구간의 반지름이 0.0093에 이른다.

윌킨슨 다항식: 서로 멀리 떨어진 단순근도 예민할 수 있다

근이 1, 2, …, 20인 다항식

윌킨슨 다항식(Wilkinson's polynomial) w(x)=(x−1)(x−2)⋯(x−20)w(x) = (x - 1)(x - 2)\cdots(x - 20)의 근은 정수 1부터 20까지로 서로 1씩 떨어져 있고 모두 단순근입니다. 겉보기에는 아무 문제가 없습니다. 이 다항식을 전개하면 x20−210x19+20615x18−⋯+20!x^{20} - 210x^{19} + 20615x^{18} - \cdots + 20!이고, 계수는 x2x^2의 계수 1.38×10191.38\times10^{19}까지 커집니다. 1959년 윌킨슨은 x19x^{19}의 계수 −210-210을 −210−2−23-210 - 2^{-23}으로, 곧 상대적으로 5.7×10−105.7\times10^{-10}만 바꾸면 근이 어떻게 되는지 계산했습니다.

결과는 충격적입니다. 아래 표는 이 장의 시뮬레이터와 같은 방법(곱 꼴로 정확히 계산한 ww에 교란 항을 더하고 에를리히–애버스 반복으로 20개 근을 동시에 구함)으로 얻은 값입니다. 근 10부터 19까지가 복소수 쌍 다섯 개로 바뀌고, 그중 하나는 실수축에서 2.81이나 떨어집니다. 이 값은 윌킨슨이 발표한 16.730737±2.812625i16.730737 \pm 2.812625i와 일치합니다.

원래 근교란 후 근∂r/∂a₁₉κr (a₁₉만)κr (모든 계수)
11.0000000008.2×10⁻¹⁸1.7×10⁻¹⁵4.2×10²
54.9999999280.612.6×10¹8.2×10⁸
88.007267603−6.0×10⁴1.6×10⁶3.9×10¹¹
98.9172502498.4×10⁵2.0×10⁷1.7×10¹²
10, 1110.095266 ± 0.643501i−7.6×10⁶ · 4.6×10⁷1.6×10⁸ · 8.9×10⁸5.6×10¹² · 1.4×10¹³
12, 1311.793634 ± 1.652330i−2.0×10⁸ · 6.1×10⁸3.5×10⁹ · 9.8×10⁹2.8×10¹³ · 4.4×10¹³
14, 1513.992358 ± 2.518830i−1.3×10⁹ · 2.1×10⁹2.0×10¹⁰ · 3.0×10¹⁰5.4×10¹³ · 5.0×10¹³
16, 1716.730737 ± 2.812625i−2.4×10⁹ · 1.9×10⁹3.2×10¹⁰ · 2.3×10¹⁰3.5×10¹³ · 1.8×10¹³
18, 1919.502439 ± 1.940330i−1.0×10⁹ · 3.1×10⁸1.2×10¹⁰ · 3.4×10⁹6.4×10¹² · 1.4×10¹²
2020.846908101−4.3×10⁷4.5×10⁸1.4×10¹¹

정리 2.5가 이유를 말해 줍니다. 근 16에서 ∂r/∂a19=−r19/w′(r)=−1619/(15!⋅4!)≈−2.4×109\partial r/\partial a_{19} = -r^{19}/w'(r) = -16^{19}/(15!\cdot4!) \approx -2.4\times10^9입니다. 분자 1619≈7.6×102216^{19} \approx 7.6\times10^{22}이 분모 ∣w′(16)∣=15! 4!≈3.1×1013|w'(16)| = 15!\,4! \approx 3.1\times10^{13}보다 압도적으로 큽니다. 상대 조건수 3.2×10103.2\times10^{10}에 교란 5.7×10−105.7\times10^{-10}을 곱하면 18로, 1차 근사가 의미 없을 만큼 큰 변화입니다. 근이 서로 충돌해 복소수로 튀어나간 것입니다.

더 놀라운 사실이 있습니다. 계수 21개 가운데 x3x^3부터 x7x^7까지의 계수 다섯 개는 2532^{53}보다 커서 배정밀도에 정확히 담기지 않습니다. 계수를 double로 저장만 해도(상대 교란 10−1610^{-16} 이하) 근 13의 오차가 6.2×10−46.2\times10^{-4}가 됩니다. "모든 계수" 열의 조건수 5.4×10135.4\times10^{13}과 uu를 곱한 6×10−36\times10^{-3}이 그 상한입니다. 그래서 고유값을 특성 다항식의 근으로 구하지 않습니다(14장). 고유값 문제 자체는 조건이 좋을 수 있는데, 다항식 계수로 옮기는 순간 훨씬 예민한 문제로 바꾸게 됩니다. 반대로 동반 행렬의 고유값으로 다항식의 근을 구하는 방법(4장)은 계수에 대해 후방 안정해서, 오차가 이 조건수가 허락하는 만큼만 생깁니다.

SIMULATOR

윌킨슨 다항식 근 흔들기: 계수 하나에 근 열 개가 튄다

복소 평면: 가로 Re, 세로 Im
교란 방식
원래 근 1, …, 20교란된 다항식의 근무작위 시행 12번(모든 계수)
교란—
가장 많이 움직인 근—
복소근 개수—
1차 예측 max κr·ε·r—
해볼 것: ① k를 60에서 줄여 가며 근 15, 16 근처가 먼저 움직이고, k = 32에서 처음으로 이웃한 근들이 만나 복소수 쌍으로 갈라지는 것을 보세요(k = 33까지는 모두 실근이지만 이미 0.3이나 움직였습니다). ② '윌킨슨 k = 23'은 위 표를 재현합니다. ③ '모든 계수'에서 k = 53(반올림 단위 수준)이면 근 11–16이 10⁻²에 가깝게 흩어집니다. 저장만 해도 생기는 오차입니다. 모델: w(z)를 곱 꼴 ∏(z − j)로 계산하고 교란 항 Σ εjajzj를 더한 뒤, 교란을 0에서 조금씩 키우며 에를리히–애버스 반복으로 근을 따라갑니다.

전방 오차와 후방 오차, 후방 안정

참값을 모르는데 알고리즘을 어떻게 평가할까

y^\hat y가 참값 yy와 얼마나 다른지(전방 오차)는 보통 알 수 없습니다. 참값을 알면 계산할 필요가 없으니까요. 그런데 거꾸로 "y^\hat y는 어떤 입력의 정확한 답인가"는 계산할 수 있는 경우가 많습니다. 선형계라면 잔차 b−Ax^b - A\hat x를 보면 되고, 근이라면 f(r^)f(\hat r)을 보면 됩니다. 이 거꾸로 된 질문이 알고리즘을 판정하는 깔끔한 자가 됩니다.

정의 2.7 (전방 오차, 후방 오차) 문제 y=f(x)y = f(x)와 알고리즘의 결과 y^\hat y에 대해
  • 전방 오차(forward error): ∣y^−y∣/∣y∣|\hat y - y|/|y|. 출력 공간에서 잰 오차.
  • 후방 오차(backward error): η(y^)=min⁡{ ∣Δx∣/∣x∣  :  y^=f(x+Δx) }\eta(\hat y) = \min\bigl\{\,|\Delta x|/|x| \;:\; \hat y = f(x + \Delta x)\,\bigr\}. y^\hat y를 정확한 답으로 갖는 입력이 원래 입력에서 상대적으로 얼마나 떨어져 있는가.
입력 공간 출력 공간 x x + Δx 후방 오차 y = f(x) ŷ = f̂(x) 전방 오차 f (정확한 문제) f̂ (알고리즘) f (정확한 문제)
그림 2-2. 후방 오차의 뜻. 알고리즘이 낸 y^\hat y를 정확한 ff가 내도록 하는 입력 x+Δxx + \Delta x를 찾는다. Δx\Delta x가 입력의 원래 불확실성(최소한 uu)보다 작다면, 알고리즘은 "데이터를 저장하는 것만큼만" 틀린 셈이다. 전방 오차는 이 Δx\Delta x를 문제의 조건수가 키운 결과다.
정의 2.8 (후방 안정) 모든 입력 xx에 대해 후방 오차가 η(f^(x))≤c u\eta(\hat f(x)) \le c\,u(상수 cc는 문제 크기에만 약하게 의존)인 알고리즘을 후방 안정(backward stable)이라 한다. 조금 약하게, y^+Δy=f(x+Δx)\hat y + \Delta y = f(x + \Delta x)이고 ∣Δy∣/∣y∣|\Delta y|/|y|, ∣Δx∣/∣x∣|\Delta x|/|x|가 모두 O(u)O(u)이면 혼합 전방–후방 안정(mixed stable), 흔히 그냥 "수치적으로 안정"이라 한다.
정리 2.9 (오차의 어림 규칙) 알고리즘의 후방 오차가 η\eta이고 문제의 상대 조건수가 κ\kappa이면
∣y^−y∣∣y∣≤κ η+O(η2).\frac{|\hat y - y|}{|y|} \le \kappa\,\eta + O(\eta^2).

증명. y^=f(x+Δx)\hat y = f(x + \Delta x), ∣Δx∣/∣x∣=η|\Delta x|/|x| = \eta. 정리 2.3을 δ=Δx/x\delta = \Delta x/x에 적용하면 된다. ∎ 후방 안정 알고리즘은 따라서 κu\kappa u 정도의 전방 오차를 낸다. 이것은 입력을 저장하는 반올림만으로도 생기는 오차이므로, 후방 안정 알고리즘은 그 문제에서 기대할 수 있는 최선이다.

가장 단순한 예는 사칙연산 자체입니다. 표준 모형 fl(x−y)=(x−y)(1+δ)\mathrm{fl}(x - y) = (x - y)(1 + \delta)는 =x(1+δ)−y(1+δ)= x(1 + \delta) - y(1 + \delta)로 읽을 수 있습니다. 입력을 각각 상대적으로 uu 이하만큼 바꾼 문제의 정확한 답이므로 뺄셈은 후방 안정합니다. 그런데도 x≈yx \approx y이면 전방 오차가 큽니다. 문제(뺄셈)의 조건수가 크기 때문입니다. "뺄셈은 불안정하다"는 말은 틀린 말이고, "x≈yx \approx y인 뺄셈은 조건이 나쁘다"가 맞는 말입니다.

예제 2.3 (호너 방법: 큰 전방 오차, 작은 후방 오차) p(x)=(x−2)9p(x) = (x - 2)^9를 전개한 계수 1,−18,144,…,−5121, -18, 144, \dots, -512로 호너 방법을 써서 계산하고, 참값 (x−2)9(x - 2)^9과 비교하라. 호너 방법은 계수를 상대적으로 γ18≈18u\gamma_{18} \approx 18u 이하만큼 바꾼 다항식의 정확한 값을 준다(정리 2.15). 다항식 값의 조건수는 ∑∣ak∣∣x∣k/∣p(x)∣=(∣x∣+2)9/∣x−2∣9\sum|a_k||x|^k/|p(x)| = (|x| + 2)^9/|x - 2|^9이다.
x호너 값참값 (x − 2)⁹상대 오차조건수상한 γ₁₈·조건수
2.50.0019531250.00195312503.9×10⁸7.7×10⁻⁷
2.19.98057×10⁻¹⁰1.00000×10⁻⁹1.9×10⁻³3.3×10¹⁴0.65
2.01−3.75167×10⁻¹²1.00000×10⁻¹⁸3.8×10⁶2.7×10²³5.4×10⁸
2.0016.25278×10⁻¹²1.00000×10⁻²⁷6.3×10¹⁵2.6×10³²5.3×10¹⁷

x=2.01x = 2.01에서 계산값은 부호조차 틀렸지만 상한 안에 있습니다. 호너 방법은 할 일을 다 했고, 전개된 계수로 근 근처의 값을 묻는 문제가 102310^{23}만큼 조건이 나쁜 것입니다. 3장 예제 3.7의 (x−1)7(x - 1)^7 잡음과 같은 현상입니다. 처방은 알고리즘이 아니라 표현을 바꾸는 것입니다. 곱 꼴 (x−2)9(x - 2)^9이나 근 근처의 테일러 계수로 저장하면 조건수가 9가 됩니다.

아래 시뮬레이터는 여러 계산에서 전방 오차를 재고, 정리 2.9를 거꾸로 써서 후방 오차를 η≈(전방 오차)/κ\eta \approx (\text{전방 오차})/\kappa로 추정합니다. 안정한 알고리즘은 η\eta가 uu 근처에 붙어 있고, 불안정한 알고리즘은 η\eta가 uu보다 훨씬 위로 올라갑니다.

SIMULATOR

후방 오차 그림: 문제 탓과 알고리즘 탓 가르기

알고리즘
전방 오차 (측정)후방 오차 η ≈ 전방/κκu: 안정 알고리즘이 낼 오차
계산값 ŷ—
조건수 κ—
전방 오차—
후방 오차 η / u—
해볼 것: ① ln(1 + x) '식 그대로'에서 κ ≈ 1인데 전방 오차가 u/x 기울기로 커집니다. η도 같이 커지므로 알고리즘 탓입니다. '다시 쓴 식'(카한의 log1p 공식)으로 바꾸면 η가 u 아래로 내려갑니다. ② 'ln x, x = 1 + t'는 반대입니다. 전방 오차는 크지만 η는 u 수준이라, 문제 탓입니다. 어떤 식으로 바꿔도 x가 double로 주어진 한 나아지지 않습니다. ③ 1 − cos x는 κ ≈ 2인데 식 그대로면 x ≈ 10⁻⁸에서 결과가 0이 됩니다. 모델: 참값은 Math.log1p, Math.expm1, 테일러 급수(1 − cos x)로 계산, η는 1차 근사.

안정한 알고리즘과 불안정한 알고리즘

조건이 좋은 문제를 불안정하게 계산하는 알고리즘은 대개 조건이 나쁜 중간 단계를 거칩니다. ln⁡(1+x)\ln(1 + x)를 "w=1+xw = 1 + x, 그다음 ln⁡w\ln w"로 계산하면 둘째 단계의 조건수가 1/∣ln⁡w∣≈1/∣x∣1/|\ln w| \approx 1/|x|입니다. 첫째 단계에서 생긴 uu의 오차가 둘째 단계에서 1/∣x∣1/|x|배로 커집니다. 문제 전체의 조건수는 1인데 알고리즘이 스스로 예민한 하위 문제를 만든 것입니다.

정리 2.10 (단계별 조건수와 오차 전파) 알고리즘이 f=g∘hf = g \circ h를 w=fl(h(x))w = \mathrm{fl}(h(x)), y^=fl(g(w))\hat y = \mathrm{fl}(g(w))로 계산하고 두 단계가 각각 상대 오차 uu 이하로 정확하면
∣y^−y∣∣y∣≲κg(h(x)) u+u.\frac{|\hat y - y|}{|y|} \lesssim \kappa_g(h(x))\,u + u.
따라서 문제의 조건수 κf=κg κh\kappa_f = \kappa_g\,\kappa_h가 작더라도 κg\kappa_g가 크고 κh\kappa_h가 작으면(둘째 단계가 첫째 단계의 반올림을 키우고, 첫째 단계는 그 반대로 줄여 주지 못하면) 불안정하다.

증명의 핵심. w=h(x)(1+δ1)w = h(x)(1 + \delta_1)이므로 정리 2.3에서 g(w)=g(h(x))(1+κgδ1+O(δ12))g(w) = g(h(x))(1 + \kappa_g\delta_1 + O(\delta_1^2)). 여기에 마지막 반올림 (1+δ2)(1 + \delta_2)를 곱한다. ∎

점화식: 같은 식, 반대 방향, 정반대의 결과

양수여야 할 적분이 −30이 되었다

In=∫01xnex−1 dxI_n = \int_0^1 x^n e^{x-1}\,dx를 n=0,1,…,20n = 0, 1, \dots, 20에 대해 구하려고 합니다. 부분 적분을 한 번 하면 In=1−nIn−1I_n = 1 - nI_{n-1}이고 I0=1−1/eI_0 = 1 - 1/e이니, 차례대로 계산하면 됩니다. 피적분 함수가 양수이므로 In>0I_n \gt 0이고, ex−1≤1e^{x-1} \le 1에서 In<∫01xndx=1/(n+1)I_n \lt \int_0^1 x^n dx = 1/(n + 1)입니다. 그런데 배정밀도로 계산하면 I18=−0.0295I_{18} = -0.0295, I20=−30.19I_{20} = -30.19가 나옵니다.

원인을 추적해 봅시다. 계산값 I^n−1=In−1+en−1\hat I_{n-1} = I_{n-1} + e_{n-1}을 점화식에 넣으면 I^n=1−nI^n−1=In−nen−1\hat I_n = 1 - n\hat I_{n-1} = I_n - n e_{n-1}이므로 오차는 en=−nen−1e_n = -n e_{n-1}, 곧

∣en∣=n! ∣e0∣.|e_n| = n!\,|e_0|.
I0I_0을 저장할 때의 오차 ∣e0∣≈10−17|e_0| \approx 10^{-17}이 20!≈2.4×101820! \approx 2.4\times10^{18}배 커져 30 정도가 된다. 각 단계의 반올림 오차도 같은 식으로 커진다. 점화식의 한 단계는 앞 값의 오차를 nn배로 키우는, 조건수 nn짜리 단계다.

처방은 점화식을 거꾸로 쓰는 것입니다. In−1=(1−In)/nI_{n-1} = (1 - I_n)/n이면 오차는 en−1=−en/ne_{n-1} = -e_n/n, 단계마다 nn으로 나뉩니다. 시작값을 아무렇게나, 예를 들어 IN=0I_N = 0으로 놓아도 오차 ∣eN∣=IN<1/(N+1)|e_N| = I_N \lt 1/(N + 1)이 N!/n!N!/n!로 나뉘어 곧 사라집니다. 베셀 함수처럼 점화식으로 계산하는 특수 함수 라이브러리가 쓰는 밀러 알고리즘(Miller's algorithm)이 이 생각입니다.

예제 2.4 (앞으로와 뒤로) 앞으로는 I0=fl(1−1/e)I_0 = \mathrm{fl}(1 - 1/e)에서, 뒤로는 I20=0I_{20} = 0과 I30=0I_{30} = 0에서 시작한다. 참값은 I100=0I_{100} = 0에서 거꾸로 계산한 값(심프슨 적분으로 10−1510^{-15}까지 확인)이다.
n참값 In앞으로 În상대 오차뒤로 (I₂₀ = 0)상대 오차뒤로 (I₃₀ = 0) 오차
00.6321205588290.632120558800.632120558800
50.1455329405730.14553294061.0×10⁻¹⁴0.145532940600
100.0838770701030.083877070065.4×10⁻¹⁰0.083877070108.1×10⁻¹³0
150.0590175408790.059033793642.8×10⁻⁴0.059017565364.2×10⁻⁷0
170.0527711191690.057191870608.4×10⁻²0.052777777781.3×10⁻⁴0
180.050119854958−0.029453670751.60.050000000002.4×10⁻³0
200.045544884076−30.192394896.6×10²0 (시작값)16.3×10⁻¹⁵

뒤로 가는 계산은 시작값이 완전히 틀렸는데도(I20=0I_{20} = 0, 상대 오차 100%) 열 단계 만에 12자리가 맞습니다. 앞으로 가는 계산은 시작값이 16자리 정확한데도 18단계 만에 부호가 틀립니다. 같은 수학 공식이 계산 방향에 따라 안정하기도, 불안정하기도 합니다.

SIMULATOR

점화식 앞으로 vs 뒤로: Iₙ = 1 − n·Iₙ₋₁

방향
앞으로: I₀의 오차
계산값의 상대 오차예측: 앞으로 n!·|e₀|, 뒤로 |eN|·n!/N!
현재 n—
계산값 În—
참값 In—
상대 오차—
해볼 것: ① '앞으로'를 끝까지 돌리면 오차가 로그 축에서 점점 가팔라지는 곡선(n!)을 따라 올라가 n ≈ 17에서 100%를 넘습니다. ② I₀의 오차를 10⁻⁴로 키우면 같은 곡선이 위로 평행 이동할 뿐입니다. 시작 오차가 얼마든 n!배가 됩니다. ③ '뒤로'에서 N을 바꿔 보세요. 시작값이 완전히 틀려도 N − n 단계 뒤에는 오차가 N!/n!로 나뉘어 사라집니다. 모델: 배정밀도 계산, 참값은 I₁₀₀ = 0에서 뒤로 계산.

급수: x < 0에서 eˣ의 테일러 급수

e⁻²⁰을 테일러 급수로

ex=∑k≥0xk/k!e^x = \sum_{k \ge 0} x^k/k!는 모든 xx에서 수렴합니다. x=−20x = -20을 넣고 항이 합을 더 바꾸지 못할 때까지 95개 항을 더하면 6.15×10−96.15\times10^{-9}이 나옵니다. 참값은 e−20=2.06×10−9e^{-20} = 2.06\times10^{-9}입니다. 한 자리도 맞지 않습니다. 급수는 수렴했고 절단 오차는 10−3010^{-30}보다 작은데 무엇이 잘못되었을까요?

항의 크기를 보면 답이 나옵니다. x=−20x = -20이면 ∣x∣k/k!|x|^k/k!는 k=19,20k = 19, 20에서 최대 4.31×1074.31\times10^7이 되고, 부호가 번갈아 바뀌는 이 큰 항들이 서로 지워져 2×10−92\times10^{-9}이 남아야 합니다. 그런데 4.3×1074.3\times10^7 크기의 수를 더할 때마다 반올림 오차가 u×4.3×107≈5×10−9u\times4.3\times10^7 \approx 5\times10^{-9}씩 생깁니다. 답보다 큰 오차입니다. 1장 정리 1.10의 합산 오차 상한 ∝u∑∣tk∣\propto u\sum|t_k|가 정확히 이 상황입니다.

∣S^−ex∣ex≲c u ∑k∣x∣k/k!ex=c u e∣x∣−x=c u e2∣x∣(x<0).\frac{|\hat S - e^x|}{e^x} \lesssim c\,u\,\frac{\sum_k |x|^k/k!}{e^{x}} = c\,u\,e^{|x| - x} = c\,u\,e^{2|x|} \quad (x \lt 0).
문제 exe^x의 조건수는 ∣x∣=20|x| = 20으로 작다. 그런데 이 알고리즘(교대 급수 합)의 증폭 인자는 e40≈2.4×1017e^{40} \approx 2.4\times10^{17}이다. 문제는 조건이 좋은데 알고리즘이 불안정한 전형적인 예다.

고치는 법은 간단합니다. x<0x \lt 0이면 ex=1/e∣x∣e^{x} = 1/e^{|x|}로 계산합니다. e∣x∣e^{|x|}의 급수는 항이 모두 양수라 상쇄가 없고(합의 조건수 1), 역수는 조건수 1입니다.

x참값 eˣ최대 항 |x|ᵏ/k!급수 그대로상대 오차1/e|x| (급수)상대 오차
−10.367879441210.36787944123.0×10⁻¹⁶0.36787944121.5×10⁻¹⁶
−56.737947×10⁻³26.06.737947×10⁻³1.2×10⁻¹³6.737947×10⁻³0
−104.539993×10⁻⁵2.76×10³4.539993×10⁻⁵2.0×10⁻⁹4.539993×10⁻⁵1.5×10⁻¹⁶
−153.059023×10⁻⁷3.35×10⁵3.059100×10⁻⁷2.5×10⁻⁵3.059023×10⁻⁷0
−202.061154×10⁻⁹4.31×10⁷6.147562×10⁻⁹2.02.061154×10⁻⁹4.0×10⁻¹⁶
−309.357623×10⁻¹⁴7.76×10¹¹6.103042×10⁻⁶6.5×10⁷9.357623×10⁻¹⁴2.7×10⁻¹⁶
SIMULATOR

e⁻ˣ 급수 vs 1/eˣ: 큰 항들의 상쇄

알고리즘
항의 크기 |tₖ|부분합의 오차 |Sₖ − eˣ|반올림 바닥 u·max|tₖ|참값 eˣ
항 수 k—
부분합 (결과)—
참값 eˣ—
상대 오차 · 예측 u·Σ|tₖ|/eˣ—
해볼 것: ① x = −20에서 '자동'을 누르면 빨간 오차가 처음에는 항을 따라 커졌다가 줄어들지만, 주황 바닥(약 5×10⁻⁹)에서 멈춥니다. 그 바닥이 초록 참값보다 위에 있으면 답이 쓸모없습니다. ② '1 / Σ |x|ᵏ/k!'로 바꾸면 바닥이 참값의 u배 아래로 내려갑니다. ③ x를 양수로 옮기면 두 방법이 같아지고 둘 다 정확합니다. 모델: 배정밀도로 tₖ = tₖ₋₁·x/k, 참값은 Math.exp.

다시 쓰기로 정확도 구하기: log1p, expm1, 근의 공식

1초마다 붙는 이자

연이율 5%를 1년에 nn번 나누어 복리로 붙이면 원금은 (1+0.05/n)n(1 + 0.05/n)^n배가 되고, n→∞n \to \infty이면 e0.05=1.0512710963760241e^{0.05} = 1.0512710963760241입니다. 이 식을 그대로 계산하면 n=1012n = 10^{12}에서 1.05122908431.0512290843으로 다섯째 자리가 틀리고, n=1015n = 10^{15}에서는 정확히 1이 나옵니다. 이자가 사라졌습니다.

정리 2.10으로 진단합니다. 첫 단계 w=1+0.05/nw = 1 + 0.05/n에서 0.05/n0.05/n의 아래 자리가 잘려 나가고(10−1510^{-15}이면 uu와 비슷한 크기), 다음 단계 wnw^n은 ww에 대해 조건수가 nn입니다. 처방은 wn=exp⁡(nln⁡(1+r/n))w^n = \exp\bigl(n\ln(1 + r/n)\bigr)으로 쓰고, ln⁡(1+z)\ln(1 + z)를 1+z1 + z를 만들지 않고 계산하는 함수 log1p(z)를 쓰는 것입니다.

n(1 + 0.05/n)ⁿ 그대로상대 오차exp(n · log1p(0.05/n))상대 오차
3651.05126749646744731.5×10⁻¹⁴1.0512674964674626< 10⁻¹⁵
10⁶1.05127109497591088.2×10⁻¹¹1.0512710950619353< 10⁻¹⁵
10⁹1.0512711007238384.1×10⁻⁹1.05127109637471< 10⁻¹⁵
10¹²1.05122908433109124.0×10⁻⁵1.0512710963760228< 10⁻¹⁵
10¹⁵14.9×10⁻²1.0512710963760241< 10⁻¹⁵

(표의 상대 오차는 같은 nn에서의 정확한 (1+0.05/n)n(1 + 0.05/n)^n에 대한 것이다.) 수치 라이브러리가 log1p와 expm1(ex−1e^x - 1)을 따로 제공하는 이유입니다. 두 함수는 작은 xx에서 조건이 좋은 문제(κ≈1\kappa \approx 1)인데, 이름 그대로의 식으로 계산하면 상쇄가 끼어드는 경우입니다.

xln(1 + x) 그대로상대 오차log1p(x)eˣ − 1 그대로상대 오차expm1(x)
10⁻⁵9.999950000398841×10⁻⁶6.6×10⁻¹²9.999950000333332×10⁻⁶1.000005000006965×10⁻⁵9.7×10⁻¹²1.000005000016667×10⁻⁵
10⁻¹⁰1.000000082690371×10⁻¹⁰8.3×10⁻⁸9.9999999995×10⁻¹¹1.000000082740371×10⁻¹⁰8.3×10⁻⁸1.00000000005×10⁻¹⁰
10⁻¹⁵1.110223024625156×10⁻¹⁵0.119.999999999999995×10⁻¹⁶1.110223024625157×10⁻¹⁵0.111.000000000000001×10⁻¹⁵
10⁻¹⁷011×10⁻¹⁷011×10⁻¹⁷

라이브러리가 없다면 어떻게 할까요? 카한이 알려 준 놀라운 요령이 있습니다. w=fl(1+x)w = \mathrm{fl}(1 + x)는 틀렸지만, 틀린 ww에 대해 ln⁡w\ln w와 w−1w - 1은 모두 정확히 계산됩니다(w−1w - 1은 스테르베즈 보조정리로 정확). 그러면 매끄러운 함수 ln⁡w/(w−1)\ln w/(w - 1)은 ww 대신 1+x1 + x에서의 값과 거의 같으므로, 여기에 xx를 곱하면 오차가 상쇄됩니다.

// 카한의 log1p: ln(1 + x)를 상쇄 없이 (작은 |x|)
w ← fl(1 + x)
if w = 1 then return x          // |x| < u: ln(1 + x) ≈ x
return ln(w) · x / (w − 1)        // ln(w)/(w − 1)은 w의 오차에 둔감

// 같은 생각의 expm1: e^x − 1
v ← exp(x)
if v = 1 then return x
if v − 1 = −1 then return −1     // x가 매우 작은 음수
return (v − 1) · x / ln(v)

앞의 시뮬레이터에서 '다시 쓴 식'이 이 두 공식입니다. 이 장과 1장에서 본 다시 쓰기를 모으면 몇 가지 원칙이 보입니다.

상쇄가 생기는 식다시 쓴 식원칙
ln(1 + x), eˣ − 1log1p(x), expm1(x), 카한 공식정보를 잃는 중간값(1 + x)을 만들지 않는다
(−b + √(b² − 4ac))/2a2c / (−b − sign(b)√(b² − 4ac))같은 부호끼리 더하고 비에트 공식으로 나눈다(1장)
√(x² + 1) − 1x² / (√(x² + 1) + 1)유리화(x = 10⁻⁸에서 그대로는 0, 다시 쓰면 5×10⁻¹⁷)
1 − cos x2 sin²(x/2)항등식으로 뺄셈을 곱셈으로
Σ xᵏ/k!, x < 01 / Σ |x|ᵏ/k!교대 급수의 상쇄를 피한다
Iₙ = 1 − n Iₙ₋₁ (앞으로)Iₙ₋₁ = (1 − Iₙ)/n (뒤로)오차를 키우는 방향 대신 줄이는 방향
(x − 2)⁹을 전개한 계수로곱 꼴, 근 근처 테일러 계수문제의 표현(기저)을 바꾼다
다시 쓰기로 고칠 수 없는 것 다시 쓰기는 알고리즘의 불안정을 고친다. 입력이 이미 반올림된 상태에서 문제의 조건이 나쁘면 어떤 식도 소용없다. 예를 들어 x=1+10−10x = 1 + 10^{-10}이 double로 주어졌다면 ln⁡x\ln x의 답은 10−610^{-6} 정도의 상대 오차를 피할 수 없다. 하지만 원래 데이터가 t=10−10t = 10^{-10}이었다면 ln⁡(1+t)\ln(1 + t)를 log1p(t)로 계산할 수 있다. 조건이 나쁜 문제는 종종 변수를 잘못 고른 문제다. 작은 양 tt를 직접 들고 다니는 편이 1+t1 + t를 들고 다니는 것보다 낫다.

행렬 조건수 맛보기

거의 평행한 두 직선

연립방정식 x+y=2x + y = 2, x+1.0001 y=2.0001x + 1.0001\,y = 2.0001의 해는 (1,1)(1, 1)입니다. 둘째 식의 우변을 측정 오차로 2.00022.0002라고 적으면(상대 변화 5×10−55\times10^{-5}) 해는 (0,2)(0, 2)가 됩니다. 해가 100% 바뀌었습니다. 두 직선이 거의 평행해서 교점이 직선을 따라 멀리 미끄러지기 때문입니다.

(1, 1) (0, 2) x + y = 2 x + 1.0001y = 2.0001 x + 1.0001y = 2.0002 (기울기 차이는 과장해서 그림)
그림 2-3. 조건이 나쁜 2×2 선형계. 두 직선이 이루는 각이 작을수록 한 직선을 조금 옮겼을 때 교점이 크게 움직인다. 행렬 조건수 κ(A)\kappa(A)가 이 각의 역수 같은 역할을 한다. 이 행렬의 κ2(A)=4.0×104\kappa_2(A) = 4.0\times10^4이다.

스칼라 조건수를 벡터로 옮기려면 크기를 재는 도구, 곧 노름이 필요합니다. 벡터 노름 ∥x∥\|\mathbf x\|과 그에 맞는 행렬 노름 ∥A∥=max⁡x≠0∥Ax∥/∥x∥\|A\| = \max_{\mathbf x \ne 0}\|A\mathbf x\|/\|\mathbf x\|(12장에서 자세히)을 쓰면 다음이 성립합니다.

정의 2.11 (행렬 조건수) 가역 행렬 AA의 행렬 조건수(matrix condition number)는 κ(A)=∥A∥ ∥A−1∥\kappa(A) = \|A\|\,\|A^{-1}\|이다. 2-노름이면 κ2(A)=σmax⁡/σmin⁡\kappa_2(A) = \sigma_{\max}/\sigma_{\min}(최대·최소 특이값의 비, LinearBook SVD)이고, 언제나 κ(A)≥1\kappa(A) \ge 1이다.
정리 2.12 (선형계의 민감도) Ax=bA\mathbf x = \mathbf b, A(x+δx)=b+δbA(\mathbf x + \delta\mathbf x) = \mathbf b + \delta\mathbf b이면
∥δx∥∥x∥≤κ(A) ∥δb∥∥b∥.\frac{\|\delta\mathbf x\|}{\|\mathbf x\|} \le \kappa(A)\,\frac{\|\delta\mathbf b\|}{\|\mathbf b\|}.

증명. δx=A−1δb\delta\mathbf x = A^{-1}\delta\mathbf b이므로 ∥δx∥≤∥A−1∥∥δb∥\|\delta\mathbf x\| \le \|A^{-1}\|\|\delta\mathbf b\|. 또 ∥b∥=∥Ax∥≤∥A∥∥x∥\|\mathbf b\| = \|A\mathbf x\| \le \|A\|\|\mathbf x\|에서 1/∥x∥≤∥A∥/∥b∥1/\|\mathbf x\| \le \|A\|/\|\mathbf b\|. 두 부등식을 곱한다. ∎ AA가 δA\delta A만큼 흔들릴 때도 1차까지 같은 κ(A)\kappa(A)가 증폭 인자다.

위 문제에서 ∥δb∥2/∥b∥2=10−4/2.83=3.5×10−5\|\delta\mathbf b\|_2/\|\mathbf b\|_2 = 10^{-4}/2.83 = 3.5\times10^{-5}, ∥δx∥2/∥x∥2=1\|\delta\mathbf x\|_2/\|\mathbf x\|_2 = 1이므로 증폭은 2.8×1042.8\times10^4배로, 상한 κ2=4.0×104\kappa_2 = 4.0\times10^4 안에 있습니다. 조건이 나쁜 행렬의 대표는 힐베르트 행렬 Hij=1/(i+j−1)H_{ij} = 1/(i + j - 1)입니다. 다항식 최소제곱 맞춤에서 자연스럽게 나오는 행렬입니다(7장). 아래 표는 Hx=H1H\mathbf x = H\mathbf 1을 부분 피벗팅 가우스 소거로 풀어 참해 1\mathbf 1과 비교한 결과입니다(κ2\kappa_2는 HH와 정확한 정수 행렬 H−1H^{-1}의 최대 고유값의 곱).

nκ₂(Hₙ)κ₂·u해의 최대 오차잔차 max|b − Hx̂|
41.55×10⁴1.7×10⁻¹²6.1×10⁻¹³1.1×10⁻¹⁶
61.50×10⁷1.7×10⁻⁹5.3×10⁻¹⁰0
81.53×10¹⁰1.7×10⁻⁶4.2×10⁻⁷4.4×10⁻¹⁶
101.60×10¹³1.8×10⁻³3.2×10⁻⁴2.2×10⁻¹⁶
121.71×10¹⁶1.90.368.9×10⁻¹⁶

이 표는 이 장 전체의 요약입니다. 잔차는 nn과 상관없이 10−1610^{-16} 수준입니다. 부분 피벗팅 가우스 소거가 (실제로는) 후방 안정하다는 뜻입니다. 해의 오차는 κ2u\kappa_2 u를 거의 그대로 따라갑니다(정리 2.9). n=12n = 12이면 배정밀도로 한 자리도 믿을 수 없는데, 알고리즘 탓이 아니라 문제 탓입니다. 그래서 잔차가 작다고 해가 정확한 것은 아닙니다. 반복 해법의 멈춤 조건(13장), 반데르몽드 행렬과 보간(5장), 정규 방정식의 조건수 제곱 문제(7장)가 모두 이 사실에서 출발합니다. 노름의 정의, 오차 한계의 증명, 반복 개선은 12장에서 다룹니다.

반올림 오차 모형과 혼합 정밀도

지금까지 "알고리즘의 후방 오차가 uu 정도"라고 말했습니다. 이것을 실제로 증명하는 도구가 1장의 표준 반올림 오차 모형입니다. 부동소수점 수 x,yx, y와 ∘∈{+,−,×,÷}\circ \in \{+, -, \times, \div\}에 대해

fl(x∘y)=(x∘y)(1+δ),∣δ∣≤u.\mathrm{fl}(x \circ y) = (x \circ y)(1 + \delta), \qquad |\delta| \le u.
긴 계산에서는 이런 (1+δi)(1 + \delta_i)가 여러 개 곱해진다. 이들을 한 덩어리로 묶는 보조정리가 오차 분석의 기본 계산 규칙이다.
정리 2.13 (반올림 인자의 곱) ∣δi∣≤u|\delta_i| \le u, ρi=±1\rho_i = \pm1이고 nu<1nu \lt 1이면
∏i=1n(1+δi)ρi=1+θn,∣θn∣≤γn:=nu1−nu.\prod_{i=1}^{n}(1 + \delta_i)^{\rho_i} = 1 + \theta_n, \qquad |\theta_n| \le \gamma_n := \frac{nu}{1 - nu}.

증명의 핵심. nn에 대한 귀납법. ρ=1\rho = 1이면 (1+θn−1)(1+δ)−1(1 + \theta_{n-1})(1 + \delta) - 1의 크기가 γn−1+u+γn−1u≤γn\gamma_{n-1} + u + \gamma_{n-1}u \le \gamma_n이고, ρ=−1\rho = -1이면 1/(1+δ)=1−δ/(1+δ)1/(1 + \delta) = 1 - \delta/(1 + \delta)로 바꿔 같은 계산을 한다. ∎ nu≪1nu \ll 1이면 γn≈nu\gamma_n \approx nu다.

정리 2.14 (내적의 후방 안정성) s=xTy=∑i=1nxiyis = \mathbf x^{\mathsf T}\mathbf y = \sum_{i=1}^{n} x_iy_i를 차례대로 곱하고 더하면
s^=∑i=1nxiyi(1+θ(i)),∣θ(i)∣≤γn,∣s^−s∣∣s∣≤γn ∑∣xiyi∣∣∑xiyi∣.\hat s = \sum_{i=1}^{n} x_iy_i(1 + \theta^{(i)}), \quad |\theta^{(i)}| \le \gamma_n, \qquad \frac{|\hat s - s|}{|s|} \le \gamma_n\,\frac{\sum|x_iy_i|}{|\sum x_iy_i|}.

증명의 핵심. 곱셈마다 (1+εi)(1 + \varepsilon_i), 덧셈마다 (1+δk)(1 + \delta_k)를 붙여 펼치면 xiyix_iy_i는 곱셈 1번과 덧셈 최대 n−1n - 1번의 반올림을 겪는다. 정리 2.13을 쓴다. ∎ 첫째 식은 "x\mathbf x를 성분마다 상대적으로 γn\gamma_n 이하만큼 바꾼 정확한 내적"이라는 뜻이므로 내적은 후방 안정하다. 둘째 식의 분수는 내적 문제의 조건수이고, 부호가 섞여 ∣s∣≪∑∣xiyi∣|s| \ll \sum|x_iy_i|이면 커진다.

정리 2.15 (호너 방법의 후방 안정성) 차수 nn인 p(x)=∑akxkp(x) = \sum a_kx^k를 호너 방법 bn=anb_n = a_n, bk=bk+1x+akb_k = b_{k+1}x + a_k로 계산하면 p^=∑kak(1+θk)xk\hat p = \sum_k a_k(1 + \theta_k)x^k, ∣θk∣≤γ2n|\theta_k| \le \gamma_{2n}이다. 따라서 ∣p^−p∣/∣p∣≤γ2n∑∣ak∣∣x∣k/∣p(x)∣|\hat p - p|/|p| \le \gamma_{2n}\sum|a_k||x|^k/|p(x)|이다(예제 2.3, 4장 호너 방법).

이 결과들은 정밀도를 바꿔 쓸 때 진가를 발휘합니다. 그래픽 처리 장치와 기계 학습 가속기는 16비트 형식을 즐겨 씁니다. 반정밀도(binary16)의 u=2−11≈4.9×10−4u = 2^{-11} \approx 4.9\times10^{-4}, bfloat16의 u=2−8≈3.9×10−3u = 2^{-8} \approx 3.9\times10^{-3}입니다(1장 표). 정리 2.14의 상한 γn\gamma_n은 반정밀도에서 n=1000n = 1000이면 0.950.95, n≥2048n \ge 2048이면 아예 무의미합니다. 실제로도 그렇습니다. 덧셈 결과가 2048을 넘으면 반정밀도의 간격이 2라 1보다 작은 항은 더해지지 않습니다.

혼합 정밀도(mixed precision)는 이 상황의 표준 처방입니다. 데이터는 낮은 정밀도로 저장하고(메모리와 대역폭이 절반 이하), 누적만 높은 정밀도로 합니다. 16비트 두 수의 곱은 가수가 22비트 이하라 단정밀도에 정확히 담기므로, 곱은 정확하고 덧셈만 단정밀도 u=6×10−8u = 6\times10^{-8}으로 반올림됩니다. 오차 상한의 uu가 누산기의 uu로 바뀌는 것입니다. 행렬 곱 전용 하드웨어(텐서 코어 등)가 "16비트 입력, 32비트 누적"으로 설계된 이유입니다.

예제 2.5 (16비트 내적) [0,1)[0, 1)의 난수 벡터 두 개를 해당 형식으로 반올림해 두고 내적을 구한다. 오차는 반올림된 입력들의 정확한 내적에 대한 상대 오차(곧 누적 과정의 오차만)다.
입력 / 누산기n = 100n = 1000n = 10000n = 10000에서의 결과
binary16 / binary162.6×10⁻³7.8×10⁻³0.251868 (참값 2497)
binary16 / binary321.5×10⁻⁷8.5×10⁻⁷2.6×10⁻⁶2496.96
bfloat16 / bfloat168.3×10⁻³0.180.90256 (합이 멈춤)
bfloat16 / binary323.1×10⁻⁸4.2×10⁻⁷2.5×10⁻⁷2497.02
binary32 / binary644.0×10⁻¹⁶1.1×10⁻¹⁵5.5×10⁻¹⁶2496.98

bfloat16 누적은 합이 256에 이르자 멈췄습니다. 256 근처에서 bfloat16의 간격은 2이고, 1보다 작은 항은 반 간격보다 작아 반올림으로 사라집니다. 1장의 조화급수가 멈춘 것과 같은 현상입니다. 누산기를 32비트로 바꾸면 같은 입력으로 오차가 백만 배 줄어듭니다. 이 생각을 선형계로 넓힌 것이 혼합 정밀도 반복 개선입니다. 낮은 정밀도로 LU 분해를 하고, 잔차 r=b−Ax^\mathbf r = \mathbf b - A\hat{\mathbf x}만 높은 정밀도로 계산해 보정 A δx=rA\,\delta\mathbf x = \mathbf r을 되풀이하면, κ(A)\kappa(A)와 낮은 정밀도의 uu의 곱이 1보다 충분히 작은 한 높은 정밀도 수준의 해를 얻습니다(12장).

SIMULATOR

혼합 정밀도 내적: 저장 정밀도와 누적 정밀도

입력 저장 형식
벡터 성분
binary16 누적bfloat16 누적binary32 누적binary64 누적상한 γₙ·Σ|xᵢyᵢ|/|s|
binary16 · bfloat16 누적—
binary32 · binary64 누적—
참 내적 (n)—
조건수 Σ|xᵢyᵢ|/|s|—
해볼 것: ① binary16 입력에서 빨간 선(16비트 누적)이 n ≈ 수천에서 꺾여 오차가 급격히 커집니다. 합이 2048을 넘은 순간입니다. ② 파란 선(32비트 누적)은 같은 입력으로 10⁻⁶ 아래에 머뭅니다. 입력 정밀도가 아니라 누적 정밀도가 오차를 정합니다. ③ '부호 섞임'으로 바꾸면 내적의 조건수가 커져 모든 선이 올라갑니다. 문제 탓인 부분입니다. 모델: 곱 xᵢyᵢ를 누산기 형식으로 반올림한 뒤 같은 형식으로 더함(가장 가까운 수로, 동률이면 짝수). 참값은 반올림된 입력들의 배정밀도 보정 합.

이 도구가 쓰이는 곳

"문제의 조건수 × 알고리즘의 후방 오차"라는 틀은 이 책의 모든 장에서 오차를 읽는 기본 문법입니다.

증상진단처방
답이 이상하다먼저 κ를 추정한다(교란 실험, 라이브러리의 조건수 추정)κu가 이미 크면 문제를 바꾼다(변수, 측정, 모델)
κ는 작은데 오차가 크다불안정한 알고리즘: 조건이 나쁜 중간 단계다시 쓰기(log1p, expm1, 비에트), 방향 바꾸기
잔차는 작은데 해가 틀리다후방 안정 + 조건이 나쁜 문제오차 ≈ κ·잔차를 보고, 정규화·전처리·높은 정밀도
점화식·급수 결과가 폭주한다오차 증폭 인자 > 1인 방향, 교대 급수의 상쇄반대 방향 점화식(밀러), 1/e|x|, 구간 축소
저정밀도 합이 멈춘다항이 합의 반 ulp보다 작다높은 정밀도 누산기, 짝짓기·카한 합산

핵심 정리

  1. 결과의 오차는 문제의 조건(입력 교란에 대한 민감도)과 알고리즘의 안정성(반올림 오차의 억제)으로 나누어 본다. 앞의 것은 알고리즘으로 고칠 수 없다.
  2. 상대 조건수 κf(x)=∣xf′(x)/f(x)∣\kappa_f(x) = |xf'(x)/f(x)|. 입력이 uu만큼 불확실하면 출력의 상대 오차는 약 κu\kappa u이고, log⁡10κ\log_{10}\kappa자리를 잃는다. 출력이 0 근처이거나 극점 근처인 곳에서 조건이 나쁘다.
  3. 뺄셈의 조건수 (∣x∣+∣y∣)/∣x−y∣(|x| + |y|)/|x - y|, 곱·나눗셈은 2. 단순근의 민감도는 g(r)/f′(r)g(r)/f'(r), mm중근은 ε1/m\varepsilon^{1/m}. 윌킨슨 다항식은 a19a_{19}를 2−232^{-23}만 바꿔도 근 10개가 복소수가 된다.
  4. 후방 오차는 "y^\hat y를 정확한 답으로 갖는 입력이 얼마나 먼가". 후방 오차가 O(u)O(u)인 알고리즘은 후방 안정이며, 전방 오차 ≲κ×\lesssim \kappa \times 후방 오차이므로 κu\kappa u 정도의 오차를 낸다. 그것이 최선이다.
  5. 조건이 좋은 문제를 불안정하게 푸는 알고리즘은 조건이 나쁜 중간 단계를 거친다. 앞으로 가는 점화식 In=1−nIn−1I_n = 1 - nI_{n-1}은 오차를 n!n!배로, x<0x \lt 0인 exe^x 급수는 e2∣x∣e^{2|x|}배로 키운다. 뒤로 가는 점화식, 1/e∣x∣1/e^{|x|}는 안정하다.
  6. 다시 쓰기(log1p, expm1, 카한 공식, 비에트 공식, 유리화, 항등식)는 알고리즘의 불안정을 고친다. 문제의 조건이 나쁘면 표현(변수, 기저)을 바꿔야 한다.
  7. 행렬 조건수 κ(A)=∥A∥∥A−1∥\kappa(A) = \|A\|\|A^{-1}\|는 선형계의 증폭 인자다. 힐베르트 행렬처럼 잔차가 10−1610^{-16}이어도 해의 오차는 κu\kappa u까지 커질 수 있다.
  8. 표준 모형 fl(x∘y)=(x∘y)(1+δ)\mathrm{fl}(x \circ y) = (x \circ y)(1 + \delta)와 γn=nu/(1−nu)\gamma_n = nu/(1 - nu)로 내적·호너의 후방 안정성을 증명한다. 저정밀도 데이터는 높은 정밀도로 누적한다(혼합 정밀도).

확인 퀴즈

Q1. x = 1.0001에서 ln x의 상대 조건수는 대략?

κ = |x · (1/x) / ln x| = 1/|ln x| = 1/ln(1.0001) ≈ 10000.5. 입력의 상대 오차가 만 배로 커진다. 출력 ln x가 0에 가까운 곳이라 조건이 나쁘다.

Q2. Math.log(1 + x)로 x = 10⁻¹⁰의 ln(1 + x)를 계산하면 상대 오차가 약 10⁻⁷이다. 이 오차의 원인은?

문제 x ↦ ln(1 + x)의 조건수는 x/((1 + x)ln(1 + x)) ≈ 1이다. 하지만 중간 단계 w = fl(1 + x)에서 상대 u의 오차가 생기고, 다음 단계 ln w의 조건수가 1/|ln w| ≈ 10¹⁰이라 오차가 10⁻⁶ 수준까지 커질 수 있다. log1p(x)를 쓰면 해결된다.

Q3. 후방 안정한 알고리즘에 대한 설명으로 옳은 것은?

정의 2.8. 전방 오차는 κ × 후방 오차 ≈ κu이므로 조건이 나쁜 문제에서는 클 수 있다(예: 힐베르트 행렬). 그래도 입력을 저장하는 오차만으로 생기는 수준이라 그 이상은 기대할 수 없다.

Q4. 조건수 10¹⁰인 선형계를 후방 안정한 알고리즘으로 배정밀도에서 풀었다. 해는 대략 몇 자리가 맞을까?

상대 오차 ≈ κu = 10¹⁰ × 1.1×10⁻¹⁶ ≈ 10⁻⁶. 16자리 중 log₁₀κ = 10자리를 잃어 약 6자리가 남는다. 힐베르트 행렬 n = 8(κ ≈ 1.5×10¹⁰)의 오차 4.2×10⁻⁷과 맞는다.

Q5. Iₙ = 1 − n·Iₙ₋₁을 I₀에서 앞으로 계산할 때 I₀의 오차 e₀는 I₂₀에서 대략 몇 배가 되는가?

eₙ = −n·eₙ₋₁이므로 |eₙ| = n!|e₀|. 뒤로 가는 점화식 Iₙ₋₁ = (1 − Iₙ)/n에서는 반대로 단계마다 n으로 나뉘어 안정하다.

Q6. 배정밀도에서 e⁻³⁰을 정확히 계산하는 방법은?

eˣ의 조건수는 |x| = 30으로 작다. 교대 급수는 최대 항 7.8×10¹¹이 상쇄되어 9.4×10⁻¹⁴을 남겨야 하므로 오차가 u × 7.8×10¹¹ ≈ 10⁻⁴로 답보다 훨씬 크다. 양항 급수의 역수는 상쇄가 없다.

Q7. 다항식의 단순근 r이 계수 교란에 예민해지는 주된 이유는?

정리 2.5: κ_r = Σ|aₖ||r|ᵏ / (|r||p′(r)|). 윌킨슨 다항식의 근은 간격이 1인데도 r = 16에서 분자 ~10²³이 분모 ~10¹³보다 압도적으로 커서 κ가 10¹⁰을 넘는다.

Q8. binary16 입력 벡터의 내적을 binary32 누산기로 계산하면 binary16 누적보다 정확해지는 이유는?

가수 11비트 두 개의 곱은 22비트 이하라 binary32(24비트)에 정확하다. 남는 오차는 누적 덧셈의 γₙ인데, 그 u가 2⁻¹¹에서 2⁻²⁴로 바뀐다. 입력의 반올림(저장 오차)과 내적의 조건수는 그대로다.