무슨 글인가요?
혹시 자바스크립트 콘솔이나 파이썬 인터프리터에서 0.1 + 0.2를 입력해본 적 있으세요? 결과가 0.3이 아니라 0.30000000000000004로 나오죠. 처음 보면 "버그 아니야?" 싶은데, 이게 사실 컴퓨터가 숫자를 저장하는 방식 때문에 생기는 정상적인 현상이에요.
오늘 소개할 글은 폴란드의 인터랙티브 그래픽 거장 바르토시 치에하노프스키(Bartosz Ciechanowski)가 쓴 "Exposing Floating Point"라는 글이에요. 2019년에 쓰였는데도 여전히 부동소수점을 설명하는 자료 중 최고로 꼽히는 명문이에요. 그냥 텍스트가 아니라, 페이지 안에서 직접 비트를 켜고 끄면서 숫자가 어떻게 변하는지 눈으로 보고 만질 수 있는 인터랙티브 콘텐츠예요.
부동소수점이 뭔데요?
살짝만 풀어볼게요. 컴퓨터는 모든 걸 0과 1로 저장하잖아요. 정수는 비교적 간단해요. 5는 101이라고 저장하면 끝. 그런데 소수점이 들어간 숫자(예: 3.14)는 어떻게 저장할까요?
방법은 두 가지예요. 첫째는 고정소수점(fixed point). "앞 16비트는 정수부, 뒤 16비트는 소수부" 이렇게 칸을 미리 나눠두는 방식이에요. 단순하지만 표현 가능한 범위가 좁아요. 둘째가 부동소수점(floating point). 과학에서 쓰는 지수 표기법(예: 1.23 × 10⁵)을 이진수에 그대로 적용한 방식이에요. "가수(mantissa)"와 "지수(exponent)"로 나눠서 저장해요. 소수점이 "떠다닌다(float)"는 의미에서 부동소수점이라는 이름이 붙었어요.
예를 들어 32비트 단정밀도(float)는 1비트 부호 + 8비트 지수 + 23비트 가수로 구성돼요. 이게 IEEE 754라는 국제 표준이고, 거의 모든 CPU와 GPU가 이 방식을 따라요.
0.1을 저장할 수 없다고요?
글의 핵심 통찰은 이거예요. 이진법으로는 0.1을 정확하게 표현할 수 없어요. 십진법에서 1/3을 0.33333...으로 무한히 써야 하는 것처럼, 이진법에서는 0.1이 0.0001100110011...로 무한히 반복되거든요. 비트 수가 유한하니까 어디선가 잘라야 하고, 그래서 컴퓨터에 저장된 "0.1"은 사실 정확한 0.1보다 아주 살짝 큰 값이에요. 마찬가지로 "0.2"도 살짝 다른 값이고, 둘을 더하면 그 오차가 합쳐져서 0.30000000000000004라는 이상한 결과가 나오는 거예요.
치에하노프스키의 글이 대단한 건, 이걸 비트 단위 슬라이더로 직접 보여준다는 점이에요. 페이지에서 부호 비트, 지수 비트, 가수 비트를 마우스로 토글하면 표현되는 실제 값이 실시간으로 바뀌어요. 그러면서 "왜 0.1은 깔끔하게 안 떨어지는지", "왜 큰 숫자는 정밀도가 떨어지는지"가 한눈에 들어와요. 글로 백 번 읽는 것보다 슬라이더 한 번 만져보는 게 훨씬 직관적이에요.
더 깊은 디테일
글에서 다루는 다른 흥미로운 주제도 많아요. 비정규수(denormal number), 무한대와 NaN(Not a Number), 반올림 모드, 언더플로/오버플로 같은 거요. 예를 들어 0.0 / 0.0은 NaN이 되는데, NaN의 특이한 점은 자기 자신과도 같지 않다는 거예요. NaN == NaN은 false예요. 이건 IEEE 754 표준에 명시된 동작이고, 자바스크립트에서 Number.isNaN() 같은 함수가 따로 있는 이유이기도 해요.
또 하나 재밌는 게 "머신 엡실론(machine epsilon)"이에요. 1.0과 그 다음으로 표현 가능한 숫자 사이의 간격을 말하는데, 이 값이 부동소수점 비교의 기준이 돼요. 그래서 두 부동소수점이 같은지 비교할 때는 a == b가 아니라 abs(a - b) < epsilon 같은 식으로 써야 해요.
왜 이게 중요한가요?
실무에서 부동소수점 오차를 모르면 진짜 무서운 버그가 나요. 가장 유명한 사례가 금융 시스템에서 절대 float를 쓰면 안 된다는 거예요. 1원이 100번 반올림되면 100원 차이가 나죠. 그래서 금융 계산은 BigDecimal(자바)이나 decimal(파이썬), numeric 타입(SQL) 같은 고정 정밀도 자료형을 써요.
게임 개발에서도 부동소수점이 중요해요. 멀티플레이어 게임에서 여러 클라이언트가 같은 물리 시뮬레이션을 돌릴 때, CPU 아키텍처에 따라 부동소수점 결과가 미세하게 달라지면 "클라이언트마다 공이 다른 곳에 떨어지는" 동기화 버그가 발생해요. 그래서 결정론적 시뮬레이션이 필요한 RTS 게임은 정수 기반 fixed point math를 따로 만들어 쓰기도 해요.
ML 쪽에서도 마찬가지예요. 요즘 LLM 학습에서 FP16, BF16, FP8 같은 저정밀도 부동소수점 포맷이 핫한 이유가, 정밀도를 일부러 낮춰서 메모리와 연산량을 아끼려는 거거든요. 부동소수점 구조를 모르면 "왜 BF16이 FP16보다 학습이 잘 되는지"(지수부가 더 넓어서 그래요) 같은 걸 이해하기 어려워요.
한국 개발자에게는?
치에하노프스키의 블로그는 모든 개발자가 한 번쯤은 정주행해야 할 보물창고예요. 이 글 말고도 카메라 렌즈, GPS, 시계 톱니바퀴, 메커니컬 워치 같은 주제를 전부 인터랙티브로 풀어놨어요. 다 영어지만 그림과 시뮬레이션이 워낙 좋아서 영어가 약해도 이해돼요.
특히 컴퓨터과학 기초가 약한 부트캠프 출신이나 비전공 개발자에게 추천해요. CS 교과서로 부동소수점 챕터를 읽으면 30분이 30시간 같은데, 이 글은 1시간 만에 평생 가는 직관을 주거든요.
마무리
0.1 + 0.2 != 0.3은 컴퓨터의 버그가 아니라 약속이에요. 그 약속의 정체를 한 번 제대로 들여다보면, 평소 무심코 쓰던 float이라는 타입이 사실은 얼마나 정교하고 동시에 위험한 도구인지 새삼 보여요.
여러분은 부동소수점 오차로 고생한 경험이 있으세요? 어떤 상황에서, 어떻게 해결하셨는지 댓글로 공유해주시면 다른 분들에게도 좋은 케이스 스터디가 될 것 같아요.
🔗 출처: Hacker News
"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"
실제 수강생 후기- 비전공자도 6개월이면 첫 수익
- 20년 경력 개발자 직강
- 자동화 프로그램 + 소스코드 제공