에듀테크 프로젝트 대시보드
부동소수점 (IEEE 754)
0.1 + 0.2 를 0.3 이라고 하지 않을까?거의 모든 언어에서 0.1 + 0.2 는 0.30000000000000004 가 나온다.
버그가 아니라, 실수(소수)를 64비트에 욱여넣는 방식(IEEE 754 double) 때문이다.
2진수로는 0.1 을 딱 떨어지게 쓸 수 없어서, 컴퓨터는 가장 가까운 값으로 반올림해 저장한다.
아래에서 숫자 하나를 넣고 그 64비트가 어떻게 조립되는지 직접 보자.
0.1 을 2진수로 쓰면 0.0001100110011… 처럼 무한 반복된다.
10진수의 1/3 = 0.333… 과 같은 상황. 52비트에서 잘리며 오차가 생긴다.
가수 비트 수는 고정이라, 수가 커지면 표현 가능한 값 사이 간격(ULP)도 커진다.
2^53 을 넘으면 정수조차 1칸씩 건너뛴다 — 2^53 + 1 은 저장이 안 된다.
지수가 전부 1이면 Infinity 또는 NaN,
전부 0이면 0 또는 아주 작은 비정규 수. 그래서 1e309 는 Infinity 가 된다.
그럼 실무에서는?
돈 계산처럼 오차가 치명적인 곳은 float 대신 정수(센트 단위) 나
Decimal 타입(파이썬 decimal, 자바 BigDecimal)을 쓴다.
비교할 때는 a == b 대신 abs(a - b) < 1e-9 처럼
허용 오차(epsilon) 를 둔다. 화면에 보여줄 때만 round(x, 2) 로 정리한다.
float 는 지수 8비트 · 가수 23비트로 약 7자리 정밀도, double 은 지수 11비트 · 가수 52비트로
약 15~16자리 정밀도다. 자바스크립트의 number, 파이썬의 float 는 모두 double이다.
C/C++ 에서 float 를 쓰면 메모리는 절반이지만 오차는 훨씬 빨리 쌓인다.
분모가 2의 거듭제곱인 분수(1/2, 1/4, 11/4)는 2진수로 딱 떨어진다.
2.75 = 10.11₂. 반대로 0.1 = 1/10, 0.3 = 3/10 처럼 분모에 5가 들어가면
2진수로 무한소수가 되어 반올림 오차가 생긴다.
저장된 0.1 은 살짝 크고, 0.2 도 살짝 크다. 둘을 더한 결과를 다시 반올림하면
0.30000000000000004 가 되는데, 이건 따로 저장한 0.3(이쪽은 살짝 작게 반올림됨)과
비트가 다르다. 두 근사값이 우연히 같지 않으면 == 는 false다.