본문으로 건너뛰기
yutils

컴퓨터는 왜 16 진수를 좋아할까?

왜 개발자들이 어디든 hex 를 만나는지, two's complement 가 어떻게 덧셈·뺄셈을 같은 회로로 만드는지, 왜 0.1 + 0.2 ≠ 0.3 인지, 모든 정수·실수 안에 숨은 비트 패턴.

약 8분 읽기

#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,012

Hex 가 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    F

16 비트 정수 = 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 hasha1b2c3... 은 sequence of hex chars
  • error code / flag — Windows 의 0x80004005 (E_FAIL)
  • bit mask0xFF00 = 상위 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
false

0.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) → false

16 진수 prefix·표기 관습

  • 0x — C / JavaScript / Python 의 표준 (0xFF = 255)
  • 0o — Python 의 octal
  • 0b — 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  // 4294967296n

2. 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)

참고 자료

요약

  • 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 인코딩 / 디코딩.
가이드 목록으로