정수 오버플로우와 부동소수점 한계
이 토픽을 마치면
정수 오버플로우가 왜 발생하는지 이해하고, 부동소수점의 정밀도 한계를 설명할 수 있으며, 수치 비교 시 주의할 점을 알게 됩니다.
컴퓨터는 숫자를 틀린다
print(0.1 + 0.2) # 0.30000000000000004print(0.1 + 0.2 == 0.3) # False!이것은 Python의 버그가 아닙니다. C, Java, JavaScript, Rust — 모든 언어에서 동일합니다. 컴퓨터가 소수를 저장하는 방식 때문입니다.
정수 오버플로우
비트 수와 범위
컴퓨터에서 정수는 정해진 비트 수로 표현됩니다.
8비트 부호 없는 정수: 0 ~ 255
8비트 부호 있는 정수: -128 ~ 127
32비트 부호 있는 정수: -2,147,483,648 ~ 2,147,483,647
64비트 부호 있는 정수: -9.2 × 10^18 ~ 9.2 × 10^18범위를 넘으면 오버플로우가 발생합니다.
오버플로우의 실체
8비트 부호 없는 정수에서 255 + 1은?
11111111 (255)
+ 00000001 (1)
----------
100000000 (256 — 하지만 9번째 비트가 잘림!)
= 00000000 (0)255 다음이 0으로 돌아갑니다! 이것이 오버플로우입니다. 자동차 주행거리계가 999,999에서 000,000으로 돌아가는 것과 같습니다.
실제 사고 사례
// C 언어 — 32비트 정수 오버플로우
int balance = 2147483647; // 최대값
balance = balance + 1; // -2147483648! (양수 → 음수로 뒤집힘)- 아리안 5 로켓 폭발 (1996): 64비트 값을 16비트로 변환 → 오버플로우 → 로켓 자폭
- 갱남스타일 YouTube 조회수 (2014): 32비트 정수 한계(21억) 초과 → Google이 64비트로 변경
Python은 오버플로우가 없다
# Python은 정수 크기에 제한이 없음big = 2 ** 100print(big) # 1267650600228229401496703205376
bigger = 2 ** 1000print(len(str(bigger))) # 302자리 수!Python은 필요에 따라 메모리를 자동으로 확장합니다. 하지만 C, Java, JavaScript 등 대부분의 언어에서는 정수 크기가 고정되어 있으므로 오버플로우에 주의해야 합니다.
NumPy도 오버플로우가 발생합니다 — NumPy 배열은 C 스타일 고정 크기 정수를 사용하기 때문입니다:
import numpy as np
a = np.int32(2147483647)print(a + 1) # -2147483648 (오버플로우!)
b = np.int64(2147483647)print(b + 1) # 2147483648 (64비트에서는 안전)부동소수점 — IEEE 754
0.1을 2진법으로 변환하면
10진법에서 1/3 = 0.333333...이 무한소수인 것처럼, 2진법에서 0.1 = 0.0001100110011...이 무한소수입니다.
0.1 (10진법) = 0.00011001100110011001100110011... (2진법, 무한 반복)컴퓨터는 유한한 비트(64비트)로 이 무한소수를 저장하므로, 어딘가에서 잘라야 합니다. 이 잘림이 오차의 원인입니다.
IEEE 754 — 64비트 부동소수점
[1비트 부호][11비트 지수][52비트 가수]
0 01111111011 1001100110011001100110011001100110011001100110011010
부호: 0 (양수)
지수: 실제 지수를 결정
가수: 유효 숫자 (52비트 ≈ 15~17자리 정밀도)# 15~17자리까지만 정확print(f"{0.1:.20f}") # 0.10000000000000000555print(f"{0.2:.20f}") # 0.20000000000000001110print(f"{0.3:.20f}") # 0.299999999999999988900.1도 정확하지 않고, 0.2도 정확하지 않으므로, 더한 결과도 정확하지 않습니다.
실전 문제와 해결
1. 비교 — 절대 ==로 비교하지 않는다
# Badif 0.1 + 0.2 == 0.3: print("Equal") # 실행 안 됨!
# Good — 허용 오차(epsilon) 사용epsilon = 1e-9if abs((0.1 + 0.2) - 0.3) < epsilon: print("Equal") # 실행됨
# Better — math.isclose 사용import mathif math.isclose(0.1 + 0.2, 0.3): print("Equal") # 실행됨2. 누적 오차 — 반복 계산에서 커진다
total = 0.0for _ in range(1000): total += 0.1print(total) # 99.99999999999857 (100이 아님!)print(f"Error: {abs(total - 100):.15f}") # 0.0000000000014323. 금융 계산 — Decimal 사용
from decimal import Decimal
# float — 오차 발생price = 0.1 + 0.2print(price) # 0.30000000000000004
# Decimal — 정확price = Decimal('0.1') + Decimal('0.2')print(price) # 0.3 (정확!)
# 주의: 문자열로 초기화해야 함Decimal(0.1) # Decimal('0.1000000000000000055511151231257827021181583404541015625')Decimal('0.1') # Decimal('0.1') — 정확금액, 세금, 이자율 — 정확성이 중요한 계산에서는 float 대신 Decimal을 씁니다.
JavaScript의 숫자 — 정수도 부동소수점
JavaScript에는 정수 타입이 없습니다. 모든 숫자가 64비트 부동소수점(IEEE 754)입니다.
// JavaScript — 큰 정수에서 정밀도 상실
console.log(9007199254740992 === 9007199254740993); // true!
// 두 수가 같다고 판단 — 52비트 가수 범위를 넘었기 때문
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1)이 때문에 JavaScript에서 BigInt가 도입되었습니다:
const big = 9007199254740993n; // BigInt 리터럴 (n 접미사)
console.log(big === 9007199254740993n); // true — 정확Twitter(현 X)의 트윗 ID가 문자열로 전달되는 이유도 이것입니다 — JSON의 숫자로 파싱하면 정밀도가 손실되므로, API가 ID를 문자열 "id_str"로도 제공합니다.
특수 값
# 무한대print(float('inf')) # infprint(float('inf') + 1) # infprint(float('inf') * -1) # -inf
# NaN (Not a Number)print(float('nan')) # nanprint(float('nan') == float('nan')) # False! (NaN은 자기 자신과도 같지 않음)
import mathprint(math.isnan(float('nan'))) # True (올바른 확인 방법)print(math.isinf(float('inf'))) # TrueNaN은 유일하게 자기 자신과 ==가 False인 값입니다. NaN 확인에는 반드시 math.isnan()을 사용합니다.
핵심 정리
| 개념 | 정리 |
|---|---|
| 정수 오버플로우 | 고정 비트 수를 넘으면 값이 뒤집힘. Python은 예외 (자동 확장) |
| IEEE 754 | 64비트 부동소수점 표준. 52비트 가수 ≈ 15~17자리 정밀도 |
| 0.1 + 0.2 ≠ 0.3 | 0.1이 2진법에서 무한소수 → 저장 시 잘림 → 오차 발생 |
| 비교 | == 대신 math.isclose() 사용 |
| 금융 계산 | float 대신 Decimal 사용 |
| NaN | 자기 자신과도 같지 않은 값. math.isnan()으로 확인 |
"컴퓨터는 계산을 정확히 한다"는 오해입니다. 컴퓨터는 유한한 비트로 무한한 수를 표현하기 때문에, 근본적으로 정밀도에 한계가 있습니다. 이 한계를 이해하면, 금융 시스템에서 Decimal을 쓰는 이유, 게임에서 좌표가 "떨리는" 이유, 과학 계산에서 오차 분석이 필요한 이유가 명확해집니다.
실전 규칙: 정수는 Python이라면 안전, 다른 언어는 범위를 확인. 소수는 == 절대 금지, 금융은 Decimal. NaN은 math.isnan()으로 확인.