1. 개요
부동소수점 연산(floating-point arithmetic)은 실수를 컴퓨터에서 유한한 비트로 근사 표현하고 계산하는 방식으로, “소수점이 떠다닌다(floating point)“는 이름 그대로 값의 크기에 따라 소수점 위치를 유동적으로 조정해 아주 큰 수와 아주 작은 수를 하나의 형식으로 다룬다. 현대 컴퓨터는 거의 예외 없이 IEEE 754 표준을 따르며, 실수 를 다음 꼴로 저장한다.
여기서 는 부호, 은 가수(mantissa, 유효숫자부), 는 지수(exponent)다. 비트를 부호·지수·가수 세 필드에 나눠 담는데, 배정밀도(double, 64비트)는 지수 11비트·가수 52비트, 단정밀도(float, 32비트)는 지수 8비트·가수 23비트를 쓴다. 핵심은 이거다. 실수는 무한히 많은데 비트는 유한하다. 그래서 대부분의 실수는 정확히 저장되지 못하고 반드시 근처의 표현 가능한 값으로 반올림된다. 이 태생적 부정확성이 모든 수치해석 정확도 논의의 출발점이며, 절단오차와 나란히 수치 오차의 두 기둥을 이룬다.1
2. IEEE 754와 표현의 구조
IEEE 754가 하는 일은 “실수를 어떻게 비트로 욱여넣고, 연산 결과를 어떻게 반올림하며, 예외 상황에서 무엇을 뱉을지”를 못박은 것이다. 표준의 주요 요소는 다음과 같다.
- 정규화 수: 가수의 맨 앞 1을 숨겨(implicit leading bit) 비트 하나를 아끼는 트릭. 배정밀도의 유효숫자가 52비트가 아니라 사실상 53비트인 이유다. 지수는 바이어스(배정밀도 1023, 단정밀도 127)를 더한 부호 없는 정수로 저장한다.
- 특수값: 과 별개의 , 오버플로 시의 , 0을 0으로 나눈 결과 같은 NaN(Not a Number). NaN은 자기 자신과도 같지 않다는 괴이한 성질이 있어서,
x != x로 NaN을 검출하는 관용구가 여기서 나온다.2 - 비정규화 수(subnormal): 0 근처의 극소값을 표현하기 위한 완충지대. 점진적 언더플로(gradual underflow)를 가능케 하지만, 하드웨어에 따라 성능이 급락하거나 컴파일러 플래그로 0으로 뭉개지기도 한다(flush-to-zero).
- 반올림 모드: 기본은 “가장 가까운 값으로, 동점이면 짝수로”(round to nearest, ties to even). 통계적 편향을 없애기 위한 선택이다. 상방·하방·0 방향 반올림도 표준에 있고, 구간 해석은 이 방향 반올림을 이용해 오차의 보증된 상하한을 만든다.
- 정확 반올림 보장의 범위: 표준은 사칙연산·제곱근·FMA(fused multiply-add)에 대해 무한 정밀도로 계산한 뒤 한 번 반올림한 값과 일치할 것을 요구한다. 반면 · 같은 초월함수는 권고(recommended) 항목이라, 구현에 따라 마지막 1~2비트가 다를 수 있다. 이 구분이 아래 재현성 절의 씨앗이다.
주요 포맷의 실제 수치를 정리하면 다음과 같다.
| 포맷 | 지수/가수 비트 | 기계 엡실론 | 십진 유효숫자 | 최대값 |
|---|---|---|---|---|
| binary64 (double) | 11 / 52 | 약 15.95자리 | ||
| binary32 (float) | 8 / 23 | 약 7.22자리 | ||
| binary16 (half) | 5 / 10 | 약 3.31자리 | 65504 | |
| bfloat16 | 8 / 7 | 약 2.41자리 |
bfloat16이 binary16보다 유효숫자가 적은데도 딥러닝에서 선호되는 이유가 이 표에 있다. 지수 비트를 단정밀도와 같은 8비트로 맞춰 표현 범위를 유지했기 때문에, 그레이디언트가 언더플로로 사라지는 사고가 훨씬 덜 난다.
3. 반올림오차와 기계 엡실론
표현 가능한 두 값 사이의 상대적 간격을 나타내는 상수가 기계 엡실론(machine epsilon, )이다. 정확히는 1보다 큰 가장 가까운 표현 가능 수와 1의 차이로, 배정밀도에서 , 단정밀도에서 이다. 실전적 의미는 “배정밀도는 약 16자리, 단정밀도는 약 7자리의 유효숫자를 신뢰할 수 있다”는 것.
임의의 실수를 반올림해 저장할 때 생기는 상대오차는 로 유계다. 이를 보통 단위 반올림(unit roundoff) 로 쓰고(배정밀도에서 ), 부동소수점 연산 의 결과를
로 모델링한다. 이 한 줄이 오차 해석의 공용 어휘다 — 알고리즘 하나의 오차 상한은 결국 이 모델을 연산 횟수만큼 곱해 정리한 결과로 나온다.
3.1. 검산: 0.1 + 0.2
배정밀도가 실제로 무엇을 저장하는지 십진수로 펼쳐 보면 논쟁이 끝난다.
- 저장된 0.1 = 0.1000000000000000055511151231257827021181583404541015625
- = 0.3000000000000000444089209850062616169452667236328125
- 저장된 0.3 = 0.299999999999999988897769753748434595763683319091796875
앞의 둘과 마지막의 차이는 로, 0.3 근처의 표현 간격(ulp) 정확히 1개다. 즉 0.1 + 0.2 == 0.3이 거짓인 이유는 계산이 틀렸기 때문이 아니라, 각 피연산자의 반올림 잔여가 합쳐지면서 결과가 이웃한 표현 가능 수로 밀렸기 때문이다. 상대오차로는 , 즉 의 두 배 이내 — 모델이 예측한 범위 그대로다.3
4. 자리수 손실 — 부동소수점의 지뢰
부동소수점에서 가장 악명 높은 함정이 자리수 손실(catastrophic cancellation)이다. 크기가 비슷한 두 수를 뺄 때, 앞쪽의 일치하는 유효숫자들이 상쇄되어 사라지고 뒤쪽의 부정확한 자리만 남아 상대오차가 폭발하는 현상이다. 각 피연산자는 멀쩡했는데 뺄셈 한 번에 유효숫자가 통째로 증발한다.
교과서적 예가 이차방정식 근의 공식이다. , , 로 두고 작은 근을
로 계산하면 배정밀도에서 가 나온다. 반면 근과 계수의 관계를 이용해
로 계산하면 이다. 참값은 뒤쪽이고, **앞쪽 식의 상대오차는 무려 25%**다. 원인은 명확하다. 와 은 앞의 15자리가 일치하므로, 빼고 나면 신뢰할 자리가 거의 남지 않는다. 식을 대수적으로 동등한 다른 형태로 바꾸기만 해서 유효숫자 8자리를 되찾은 셈이다.4 같은 이유로 수치미분의 , 자코비안 행렬의 유한차분 근사가 스텝 를 지나치게 줄이면 오히려 부정확해진다.
5. 합산 오차와 카한 합산
부동소수점 덧셈은 결합법칙이 성립하지 않는다. 일 수 있다. 확인은 한 줄로 끝난다 — 배정밀도에서 이지만 이고, 이지만 이다.
개 값을 순진하게 누적하면 오차 상한은 로 항 수에 비례해 커진다. 실제로 0.1을 천만 번 누적하면 999999.9998389754가 나와 절대오차 (상대오차 )를 얻는다. 위 상한 안에 얌전히 들어간다.
카한 합산(Kahan compensated summation, 1965)은 매 덧셈에서 잘려 나간 저차 비트를 보정 변수에 담아 다음 덧셈에 되돌린다.
s = 0; c = 0
각 x에 대해:
y = x - c
t = s + y
c = (t - s) - y # 이번에 잘려 나간 양
s = t
오차 상한이 항 수와 거의 무관한 로 바뀐다. 위 천만 항 예제를 카한 합산으로 돌리면 정확히 1000000.0이 나온다. 더 강하게는 부분합을 확장 정밀도로 모으는 방식(파이썬 math.fsum, 노이마이어 변형)이 있어 이쪽도 정확값을 준다. 대가는 덧셈당 연산 3~4배와 벡터화 방해이므로, 실무에서는 누적 길이가 길고 값의 크기 차이가 큰 곳(에너지 총합, 잔차 노름, 질량 보존 점검)에만 골라 쓴다. 보정 변수를 계산하는 줄이 -ffast-math 최적화에 삭제되는 유명한 함정을 포함해, 정밀도를 올리는 쪽 대안과의 비교는 혼합 정밀도가 정리해 두었다.
6. 재현성 — 같은 프로그램이 같은 답을 내지 않는 이유
“IEEE 754를 따르니 어느 CPU에서든 같은 답이 나온다”는 말은 절반만 참이다. 표준이 보장하는 것은 개별 연산의 정확 반올림뿐이고, 프로그램 전체의 비트 단위 재현성은 그 위에 얹힌 여러 자유도에 좌우된다.
- 연산 순서: 병렬 컴퓨팅에서 스레드 수나 스케줄에 따라 리덕션 순서가 바뀌면 결합법칙 위반이 그대로 답의 마지막 자리로 나타난다. 실행마다 답이 미세하게 달라지는 비결정성(non-reproducibility)의 주범이며, 해결책은 고정 순서 리덕션이나 재현 가능한 합산 알고리즘이다.
- FMA 융합: 컴파일러가
a*b+c를 FMA 한 명령으로 합치면 중간 반올림이 한 번 사라져 결과가 달라진다. 보통 더 정확하지만, “달라진다”는 사실 자체가 회귀 시험을 깨뜨린다. - 확장 정밀도 레지스터: 구형 x86의 x87 FPU는 80비트 내부 정밀도로 계산해, 중간값이 메모리에 쓰일 때야 배정밀도로 접힌다. 같은 코드가 레지스터 할당에 따라 다른 답을 내던 고전적 원흉이다(SSE2 이후로는 대부분 사라졌다).
- 초월함수 구현: 앞서 적은 대로 ··는 정확 반올림이 의무가 아니다. libm이 다르면(OS·버전·GPU) 마지막 비트가 다를 수 있다.
- 컴파일러 플래그:
-ffast-math계열은 결합법칙 재배열·비정규화 수 flush·역수 곱셈 치환을 허용한다. 속도는 얻지만 표준 준수를 버리는 것이라, 수치 검증이 필요한 코드에서는 켜기 전에 반드시 영향을 확인해야 한다.
따라서 검증 및 확인 관점에서 “같은 결과”를 요구할 때는 비트 단위 동일인지 허용오차 내 동일인지를 먼저 정의해야 한다. 대부분의 CFD·구조해석 회귀 시험은 후자를 쓴다.
7. 수치해석 정확도에서의 함의
부동소수점의 유한 정밀도는 수치해석 전반에 그림자를 드리운다.
- 두 종류의 오차. 방법 자체에서 오는 절단오차와, 표현·연산에서 오는 반올림오차는 성격이 다르다. 스텝 를 줄이면 절단오차는 줄지만 반올림오차는 커진다. 그래서 총오차를 최소화하는 최적 가 존재하며(전진차분이면 , 즉 배정밀도에서 부근), “무조건 촘촘히”가 정답이 아니다.
- 조건수와의 합작. 조건수가 큰 악조건 문제에서는 입력의 반올림오차마저 배로 증폭된다. 상대오차가 대략 가 되므로 이면 배정밀도 약 16자리 중 자리를 잃는다. 이면 남는 것이 6자리 — 희소행렬 선형계를 풀기 전에 조건수를 한 번 가늠해 보라는 조언이 여기서 나온다.
- 후방 안정성. 알고리즘이 “약간 다른 입력에 대한 정확한 답”을 준다면 후진 안정(backward stable)하다고 한다. LU 분해의 부분 피벗팅이나 하우스홀더 QR 분해가 실무에서 신뢰받는 이유가 조건수와 무관하게 이 성질을 갖기 때문이다. 오차의 책임을 알고리즘과 문제로 나눠 묻는 이 관점이 현대 수치선형대수의 표준 언어이며, 자세한 것은 후진 오차 해석에 있다.
- 동등 비교 금지. 부동소수점 값을
==로 비교하는 것은 초보의 대표적 실수다. 반드시 허용오차를 두고 비교하되, 절대오차와 상대오차를 섞어() 쓰는 것이 관례다. 반복법의 수렴 판정 기준도 같은 형태다. - 정밀도 선택과 혼합정밀. 메모리와 속도가 아쉬운 전산과학·GPU 연산에서는 단정밀도나 반정밀도를 쓰기도 하지만, 잔차가 정밀도 한계에 부딪혀 더 안 내려가는 “정밀도 바닥”을 만나면 배정밀도로 갈아타야 한다. 낮은 정밀도로 분해를 만들고 배정밀도로 잔차를 계산해 보정하는 혼합 정밀도 반복 개선(mixed-precision iterative refinement)은 텐서코어 시대에 다시 주류가 됐다.
결론적으로, 컴퓨터가 뱉는 숫자는 실수가 아니라 실수의 그림자다. 그 그림자가 실물과 얼마나 다른지를 아는 것이 수치해석의 절반이고, 나머지 절반은 그 차이를 통제하는 기술이다. V&V 정신은 여기서도 유효하다 — 믿되, 검증하라.
8. 관련 문서
- 절단오차 · 조건수 · 테일러 급수
- 수치미분 · 자코비안 행렬 · 뉴턴-랩슨법
- LU 분해 · QR 분해 · 희소행렬
- 반복법 · 수치적분 · 구간 해석
- 병렬 컴퓨팅 · 전산과학 · 혼합 정밀도
- 후진 오차 해석 · 검증 및 확인
9. Footnotes
-
“왜 내 계산 결과 마지막 자리가 이상하죠?”라는 질문의 99%는 여기서 답이 난다. 컴퓨터는 실수를 모른다. 유리수의 부분집합, 그것도 분모가 2의 거듭제곱인 특수한 유리수만 정확히 안다. ↩
-
NaN은 “전염성”이 있다. NaN이 낀 연산은 대부분 NaN을 뱉으므로, 해석 결과 어딘가에 NaN이 하나 생기면 순식간에 필드 전체가 NaN으로 물든다. 그래서 솔버가 발산할 때 잔차창에 뜨는 그 반가운(?) 세 글자, NaN이다. 반대로 이 전염성은 공짜 경보기라, NaN 워치독 한 줄이 디버깅 반나절을 아껴 준다. ↩
-
이 현상만 모아 놓은
0.30000000000000004.com이라는 사이트가 실제로 있다. 언어별로 같은 값을 뱉는 목록을 보면, 이게 언어 탓이 아니라 하드웨어 표준 탓이라는 게 한눈에 들어온다. ↩ -
부동소수점의 함정을 가장 잘 아는 사람이 표준을 만든 건 다행스러운 일이다. IEEE 754 설계를 주도한 윌리엄 카한(William Kahan)은 그 공로로 1989년 튜링상을 받았고, 이 문서에 나오는 카한 합산도 그의 이름을 딴 것이다. ↩
이 문서의 주요 출처
- IEEE 754-2019 — IEEE Standard for Floating-Point Arithmetic
- D. Goldberg, "What Every Computer Scientist Should Know About Floating-Point Arithmetic", ACM Computing Surveys 23(1), 1991
- N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed. (SIAM, 2002)
- J.-M. Muller et al., Handbook of Floating-Point Arithmetic, 2nd ed. (Birkhäuser, 2018)
- W. Kahan, "Lecture Notes on the Status of IEEE Standard 754 for Binary Floating-Point Arithmetic" (1997)
의견을 반영할게요. 감사합니다.