#FF0000 (빨강), 0xDEADBEEF (메모리 디버깅 시 자주 보임), 0x7FFFFFFF (32-bit int 의 최댓값) — 개발자들은 어디서나 hex 를 만난다. 왜 컴퓨터는 16 진수를 좋아할까? 그리고 왜 0.1 + 0.2 ≠ 0.3이 되는 걸까? 이 가이드는 binary / hex / two's complement / IEEE 754 float — 모든 정수와 실수 안에 숨은 비트 패턴을 풀어본다.
왜 진법이 필요한가
컴퓨터의 내부 = 0/1 만. 하지만 사람이 32 비트 정수를 0/1 로 읽으면 무리:
0011 1001 0101 1010 1110 0100 1100 1100 (32 bit)
↑ 사람이 한 눈에 못 본다같은 값을 다른 진법으로:
Binary: 00111001 01011010 11100100 11001100
Hex: 39 5A E4 CC
Decimal: 962,360,012Hex 가 binary 와 정확히 정합 (4 bit = 1 hex digit). 시각 압축 효율이 가장 좋아 컴퓨터·개발자 친화.
왜 hex 인가 — 16 = 2^4 의 마법
4 bit = 정확히 1 hex digit. 변환이 자명:
Binary Hex
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 A
1011 B
1100 C
1101 D
1110 E
1111 F16 비트 정수 = 4 hex digit. 32 비트 = 8 hex digit. 64 비트 = 16 hex digit. 직관적 매핑.
Decimal 이라면 — 16 = 2^4 같이 깔끔하지 않음. 2^10 = 1024 ≈ 1000 — 컴퓨터 1 KiB 가 1 KB 와 다른 이유. Octal (8 진수) 도 가능 (3 bit = 1 octal digit) 하지만 4 bit 가 1 byte 와의 정합성에서 hex 압도.
실험 — 진수 변환기 가 같은 값의 binary / octal / decimal / hex 동시 표시. Hex 인코딩 / 디코딩 가 text ↔ hex 양방향.
어디서 hex 가 등장하나
- 색 코드 —
#RRGGBB= 각 채널 8 bit (00-FF).#FF6B35= R 255, G 107, B 53. - 메모리 주소 — debugger / crash dump 의
0x7FFFEE00 - SHA / MD5 / UUID hash —
a1b2c3...은 sequence of hex chars - error code / flag — Windows 의
0x80004005(E_FAIL) - bit mask —
0xFF00= 상위 byte 만,0x000F= 하위 nibble - UTF-8 byte — 한글 "안" =
EC 95 88 - magic numbers —
0xDEADBEEF,0xCAFEBABE(Java class file),0x89 50 4E 47(PNG header) - 비밀번호 해시 표시 — bcrypt 의
$2b$12$...의 일부도
양수 + 음수 = Two's Complement
양의 정수만 다루기는 쉽다. 5 = 0101. 음수는?
Naive 접근 — sign bit (가장 위 비트). 0 = +, 1 = -:
+5 = 0101
-5 = 1101 ← sign bit 만 1?문제:
- +0 (
0000) 와 -0 (1000) 두 표현. 같은 값에 두 비트 패턴. - 덧셈 회로 복잡.
+5 + (-5) = -10(binary 가산 시 0 아님).
해결 — two's complement. 음수 표현:
-X = (NOT X) + 1
예: -5 (4 bit)
+5 = 0101
NOT = 1010
+ 1 = 1011
즉 -5 = 1011
확인 — 5 + (-5):
0101
+ 1011
────
10000 ← 5 bit (overflow), 하위 4 bit = 0000 = 0 ✓핵심 효과:
- +0 만 존재 (
0000) - 덧셈·뺄셈 회로가 같음. CPU 에 별도 뺄셈 회로 불필요 — 부호만 바꿔 더하기.
- 비교 — sign bit 검사 + 일반 비교
각 비트 폭의 범위
8-bit (byte): -128 ~ +127 (= -2^7 ~ 2^7 - 1)
16-bit (short): -32,768 ~ +32,767 (= -2^15 ~ 2^15 - 1)
32-bit (int): -2.1 billion ~ +2.1 billion (= -2^31 ~ 2^31 - 1)
64-bit (long): -9.2 × 10^18 ~ +9.2 × 10^18 (= -2^63 ~ 2^63 - 1)양수 범위가 음수보다 1 작은 이유 — 0 이 양수 영역에 박힘.
유명한 overflow 사고
// 32-bit int 의 maximum + 1
0x7FFFFFFF + 1 = 0x80000000
= 2147483647 + 1 = -2147483648 (음수로 wrap!)
// Y2038 — Unix timestamp 가 int32 일 때
2038-01-19 03:14:07 UTC 가 마지막
다음 초 → -2147483648 → 1901년 12월 13일
// Java 의 Integer.MIN_VALUE
Math.abs(-2147483648) === -2147483648 ← 양수가 안 됨!실수 — IEEE 754 의 비밀
정수만으로는 부족. 0.1, π, 1.5 × 10^200 같은 실수 어떻게 저장하나?
IEEE 754 (1985 표준) — 과학 표기법의 비트 버전:
x = (-1)^sign × 1.mantissa × 2^exponent
32-bit float (single):
┌─┬───────────┬─────────────────────────────┐
│S│ exponent │ mantissa │
│ │ (8 bit) │ (23 bit) │
└─┴───────────┴─────────────────────────────┘
1 bit = 32 bit
64-bit double:
┌─┬──────────────┬──────────────────────────────────────────────────┐
│S│ exponent │ mantissa │
│ │ (11 bit) │ (52 bit) │
└─┴──────────────┴──────────────────────────────────────────────────┘
1 bit = 64 bit예 — 1.5 의 64-bit double:
1.5 = 1.1₂ × 2^0
S = 0 (positive)
exponent = 0 + 1023 (bias) = 1023 = 01111111111
mantissa = 1000...0 (소수점 뒤 0.1₂ = 0.5 의 첫 비트 1)
전체 = 0 01111111111 1000000000000000000000000000000000000000000000000000
= 0x3FF8000000000000왜 0.1 + 0.2 ≠ 0.3 인가
> 0.1 + 0.2
0.30000000000000004
> 0.1 + 0.2 === 0.3
false0.1 이 binary 로 표현 불가능. 1/10 을 2 진수로 표현하면 무한 순환:
0.1 (decimal) = 0.0001100110011001100... (binary, repeating)
54 bit 절단 (double):
0.1 ≈ 0.1000000000000000055511151231257827021181583404541015625
0.2 ≈ 0.2000000000000000111022302462515654042363166809082031250
합: 0.3000000000000000166533453693773481063544750213623046875
≈ 0.30000000000000004해결 — 금융 계산은 BigDecimal / Decimal128. 비교는 epsilon:
Math.abs(a - b) < 1e-9 // float 비교
// 또는 정수 + 단위
// $0.10 + $0.20 대신 10 cent + 20 cent특수 값
NaN (Not a Number):
0 / 0, Math.sqrt(-1)
NaN === NaN → false (유일한 자기 동등성 실패)
isNaN(x) 또는 Number.isNaN(x)
Infinity:
1 / 0 = Infinity
-1 / 0 = -Infinity
±0 (positive zero, negative zero):
0 === -0 → true
1 / 0 === 1 / -0 → false (Infinity vs -Infinity)
Object.is(0, -0) → false16 진수 prefix·표기 관습
0x— C / JavaScript / Python 의 표준 (0xFF= 255)0o— Python 의 octal0b— binary (0b1011)#— CSS 의 color (#FF6B35)$— assembly 일부 ($FF)&h— VBA / Visual Basic
underscore 는 자릿수 가독성: 0xFFFF_FFFF_DEAD_BEEF (Python / Rust / 새 JavaScript).
흔한 함정
1. JavaScript 의 비트 연산자 32-bit 강제
0x100000000 | 0 // 4294967296 | 0 = 0 ← 32 bit 로 잘림!
// overflow 후 wrap
// 64-bit 비트 연산은 BigInt
0x100000000n | 0n // 4294967296n2. parseInt 의 진법 추측 (옛 환경)
parseInt("010") // 옛 브라우저: 8 (octal로 해석!)
parseInt("010", 10) // 10 ← radix 명시 권장
parseInt("0xff") // 255 (hex 자동 감지)3. signed vs unsigned shift
-1 >> 1 // -1 (sign bit 유지, arithmetic shift)
-1 >>> 1 // 2147483647 = 0x7FFFFFFF (zero fill, logical shift)4. Float comparison
// Bad
if (price * 1.1 === expected) { ... }
// Good
if (Math.abs(price * 1.1 - expected) < 0.001) { ... }
// Best (금융)
priceInCents * 110 / 100 // 정수 연산5. NaN 의 비교 함정
NaN === NaN // false
NaN !== NaN // true (유일한 NaN 식별 방법)
isNaN("foo") // true (string 도 NaN 으로 변환되니!)
Number.isNaN("foo") // false (strict)참고 자료
- IEEE 754 — Wikipedia
- Two's complement — Wikipedia
- 0.30000000000000004.com — float 정밀도 사이트
- Goldberg 1991 — "What Every Computer Scientist Should Know About Floating-Point" — Oracle docs
요약
- Binary = 컴퓨터 내부. Hex = 사람이 binary 를 읽는 압축 (4 bit = 1 hex digit).
- Hex 가 모든 곳에 — 색·메모리·hash·flag·UTF-8 byte·magic number.
- Two's complement — 음수 표현. 덧셈·뺄셈 회로가 같음. -0 없음. n-bit 범위 -2^(n-1) ~ 2^(n-1) - 1.
- Overflow = bit 한계 넘어가면 wrap. Y2038 / int32 max+1 = 음수.
- IEEE 754 float — sign + exponent + mantissa. 0.1 + 0.2 ≠ 0.3 은 0.1 이 binary 무한 순환 때문.
- 금융은 BigDecimal 또는 정수 (cent) 단위. float 비교는 epsilon.
- NaN, Infinity, ±0 — IEEE 754 의 특수 값. 비교 시 주의.
- 실험 — 진수 변환기 / Hex 인코딩 / 디코딩.