1. 개요[편집]
부동소수점 연산(floating-point arithmetic)은 실수를 컴퓨터에서 유한한 비트로 근사 표현하고 계산하는 방식으로, “소수점이 떠다닌다(floating point)“는 이름 그대로 값의 크기에 따라 소수점 위치를 유동적으로 조정해 아주 큰 수와 아주 작은 수를 하나의 형식으로 다룬다. 현대 컴퓨터는 거의 예외 없이 IEEE 754 표준을 따르며, 실수 를 다음 꼴로 저장한다.
여기서 는 부호, 은 가수(mantissa, 유효숫자부), 는 지수(exponent)다. 비트를 부호·지수·가수 세 필드에 나눠 담는데, 배정밀도(double, 64비트)는 지수 11비트·가수 52비트, 단정밀도(float, 32비트)는 지수 8비트·가수 23비트를 쓴다. 핵심은 이거다. 실수는 무한히 많은데 비트는 유한하다. 그래서 대부분의 실수는 정확히 저장되지 못하고 반드시 근처의 표현 가능한 값으로 반올림된다. 이 태생적 부정확성이 모든 수치해석 정확도 논의의 출발점이다.1
2. IEEE 754와 표현의 구조[편집]
IEEE 754가 하는 일은 “실수를 어떻게 비트로 욱여넣고, 연산 결과를 어떻게 반올림하며, 예외 상황에서 무엇을 뱉을지”를 못박은 것이다. 덕분에 같은 프로그램이 인텔 CPU에서든 ARM에서든 (원칙적으로는) 같은 답을 낸다. 표준의 주요 요소는 다음과 같다.
- 정규화 수: 가수의 맨 앞 1을 숨겨(implicit leading bit) 비트 하나를 아끼는 트릭. 배정밀도의 유효숫자가 52비트가 아니라 사실상 53비트인 이유.
- 특수값: 과 별개의 , 오버플로 시의 , 0을 0으로 나눈 결과 같은 NaN(Not a Number). NaN은 자기 자신과도 같지 않다는 괴이한 성질이 있어서,
x != x로 NaN을 검출하는 관용구가 여기서 나온다.2 - 비정규화 수(subnormal): 0 근처의 극소값을 표현하기 위한 완충지대. 점진적 언더플로(gradual underflow)를 가능케 하지만, 하드웨어에 따라 성능이 급락하기도 한다.
- 반올림 모드: 기본은 “가장 가까운 값으로, 동점이면 짝수로”(round to nearest, ties to even). 통계적 편향을 없애기 위한 선택이다.
3. 반올림오차와 기계 엡실론[편집]
표현 가능한 두 값 사이의 상대적 간격을 나타내는 상수가 기계 엡실론(machine epsilon, )이다. 정확히는 1보다 큰 가장 가까운 표현 가능 수와 1의 차이로, 배정밀도에서 , 단정밀도에서 이다. 실전적 의미는 “배정밀도는 약 16자리, 단정밀도는 약 7자리의 유효숫자를 신뢰할 수 있다”는 것.
임의의 실수를 반올림해 저장할 때 생기는 상대오차는 로 유계다. 이를 보통 단위 반올림(unit roundoff) 로 쓰고, 부동소수점 연산 의 결과를
로 모델링한다. 즉 개별 연산 하나하나의 오차는 아주 작다. 문제는 이 작은 오차들이 수백만 번의 연산을 거치며 어떻게 쌓이고 증폭되느냐다. 그리고 그 증폭의 주범이 다음 절의 자리수 손실이다.
4. 자리수 손실 — 부동소수점의 지뢰[편집]
부동소수점에서 가장 악명 높은 함정이 자리수 손실(catastrophic cancellation)이다. 크기가 비슷한 두 수를 뺄 때, 앞쪽의 일치하는 유효숫자들이 상쇄되어 사라지고 뒤쪽의 부정확한 자리만 남아 상대오차가 폭발하는 현상이다. 각 피연산자는 멀쩡했는데 뺄셈 한 번에 유효숫자가 통째로 증발한다.
교과서적 예가 이차방정식 근의 공식 이다. 이고 가 작으면 에서 크기가 비슷한 두 양이 빼져 자리수 손실이 일어난다. 그래서 수치적으로 견고한 구현은 부호가 안전한 쪽 근을 먼저 구하고 근과 계수의 관계()로 나머지 근을 얻는다.3 같은 이유로 수치미분의 , 자코비안 행렬의 유한차분 근사가 스텝 를 지나치게 줄이면 오히려 부정확해진다.
또 하나 반드시 새길 것. 부동소수점 덧셈은 결합법칙이 성립하지 않는다. 일 수 있다. 그래서 큰 배열의 합을 순진하게 누적하면 오차가 쌓이는데, 이를 보정하는 카한 합산(Kahan summation) 같은 기법이 존재한다. 병렬 컴퓨팅에서 스레드마다 더하는 순서가 달라지면 실행할 때마다 답의 마지막 몇 자리가 달라지는 “비결정성(non-reproducibility)” 문제도 근원은 같다.
5. 수치해석 정확도에서의 함의[편집]
부동소수점의 유한 정밀도는 수치해석 전반에 그림자를 드리운다.
- 두 종류의 오차. 방법 자체에서 오는 테일러 급수 절단오차와, 표현·연산에서 오는 반올림오차는 성격이 다르다. 스텝 를 줄이면 절단오차는 줄지만 반올림오차는 커진다. 그래서 총오차를 최소화하는 최적 가 존재하며, “무조건 촘촘히”가 정답이 아니다.
- 조건수와의 합작. 조건수가 큰 악조건 문제에서는 입력의 반올림오차마저 배로 증폭된다. 이면 배정밀도 16자리 중 약 자리를 잃는다는 경험칙이 바로 이 반올림오차와 조건수의 곱에서 나온다.
- 동등 비교 금지. 부동소수점 값을
==로 비교하는 것은 초보의 대표적 실수다. 가 과 정확히 같지 않은 것은 밈이 될 정도로 유명하다.4 반드시 허용오차(tolerance)를 두고 로 비교해야 한다. 반복법의 수렴 판정 기준도 절대·상대 허용오차를 섞어 잡는다. - 정밀도 선택. 메모리와 속도가 아쉬운 계산물리·GPU 연산에서는 단정밀도나 반정밀도(half)를 쓰기도 하지만, 잔차가 정밀도 한계에 부딪혀 더 안 내려가는 “정밀도 바닥(precision floor)“을 만나면 배정밀도로 갈아타야 한다. 최근에는 낮은 정밀도로 대충 풀고 배정밀도로 보정하는 혼합정밀(mixed-precision) 반복 개선 기법이 각광받는다.
결론적으로, 컴퓨터가 뱉는 숫자는 실수가 아니라 실수의 그림자다. 그 그림자가 실물과 얼마나 다른지를 아는 것이 수치해석의 절반이고, 나머지 절반은 그 차이를 통제하는 기술이다. V&V 정신은 여기서도 유효하다 — 믿되, 검증하라.
6. 관련 문서[편집]
7. Footnotes[편집]
-
“왜 내 계산 결과 마지막 자리가 이상하죠?”라는 질문의 99%는 여기서 답이 난다. 컴퓨터는 실수를 모른다. 유리수의 부분집합, 그것도 2의 거듭제곱을 밑으로 하는 특수한 유리수만 정확히 안다. ↩
-
NaN은 “전염성”이 있다. NaN이 낀 연산은 대부분 NaN을 뱉으므로, 해석 결과 어딘가에 NaN이 하나 생기면 순식간에 필드 전체가 NaN으로 물든다. 그래서 솔버가 발산할 때 잔차창에 뜨는 그 반가운(?) 세 글자, NaN이다. ↩
-
이 자리수 손실 예방 기법은 윌리엄 카한(William Kahan)이 IEEE 754 표준 설계를 주도하며 널리 알린 것으로, 그는 이 공로로 1989년 튜링상을 받았다. 부동소수점의 함정을 가장 잘 아는 사람이 표준을 만들었다는 것은 다행스러운 일이다. ↩
-
과 는 이진 부동소수점으로 정확히 표현되지 않는다(무한 순환 이진소수). 그래서 둘을 더하면 같은 값이 나온다. 이 현상만 모아 놓은
0.30000000000000004.com이라는 사이트가 실제로 있을 정도다. ↩