Binary: 2진수 표현과 컴퓨터가 데이터를 저장하는 방식
컴퓨터는 모든 데이터를 0과 1로만 표현하는 이진법(Binary)을 사용한다. 정수, 실수, 문자, 심지어 명령어까지 비트(bit) 조합으로 인코딩되며, 이 규칙을 이해해야 오버플로우, 부동소수점 오차, 비트 연산 최적화 같은 실무 문제를 정확히 진단할 수 있다. 이 글에서는 2진수 표현 방식과 그 원리, 그리고 실제 코드에서의 활용을 다룬다.
1. 개념
이진법(Binary, base-2)은 0과 1 두 가지 숫자만으로 모든 값을 표현하는 수 체계다. 컴퓨터의 물리적 회로는 전압의 높고 낮음(on/off) 두 상태만 안정적으로 구분할 수 있기 때문에, 이 두 상태를 0과 1로 매핑해서 모든 데이터를 표현한다.
우리가 일상에서 쓰는 십진법(decimal, base-10)이 0~9까지 10개 숫자로 자릿수를 올리듯이, 이진법은 0과 1만으로 자릿수를 올린다. 예를 들어 십진수 13은 이진수로 1101이다.
13 = 1×2³ + 1×2² + 0×2¹ + 1×2⁰
= 8 + 4 + 0 + 1
= 132. 왜 사용하는가
- 물리적 안정성: 트랜지스터는 전압이 정확히 중간값인지 판별하기 어렵다. 하지만 "임계값 이상이면 1, 이하면 0"처럼 두 상태만 구분하면 노이즈에 강하고 오류율이 낮다.
- 논리 회로와의 직결성: 불(Boolean) 대수의 AND, OR, NOT 연산이 그대로 0과 1의 조합으로 구현되어, 회로 설계와 소프트웨어 논리가 자연스럽게 연결된다.
- 저장 효율과 연산 속도: 비트 단위 연산(AND, OR, XOR, SHIFT)은 CPU에서 가장 빠른 연산에 속하며, 플래그 처리나 권한 관리 같은 곳에서 메모리와 연산 비용을 크게 줄인다.
3. 동작 원리
3.1 진법 변환
십진수를 이진수로 바꾸려면 2로 나눈 나머지를 역순으로 읽는다.
13 ÷ 2 = 6 나머지 1
6 ÷ 2 = 3 나머지 0
3 ÷ 2 = 1 나머지 1
1 ÷ 2 = 0 나머지 1
나머지를 아래에서 위로 읽으면: 11013.2 음수 표현: 2의 보수(two's complement)
컴퓨터는 음수를 2의 보수 방식으로 표현한다. n비트 정수에서 음수는 "해당 값의 절댓값을 이진수로 표현한 뒤, 모든 비트를 반전(1의 보수)하고 1을 더한" 값이다.
8비트 기준 -5 표현 과정:
5 = 0000 0101
1의 보수 = 1111 1010
+1 = 1111 1011 (이것이 -5)2의 보수를 쓰는 이유는 덧셈 회로 하나로 뺄셈까지 처리할 수 있기 때문이다. a - b는 a + (-b)로 치환되고, 하드웨어는 덧셈기(adder) 하나만 구현하면 된다.
3.3 부동소수점: IEEE 754
실수는 IEEE 754 표준에 따라 부호(sign), 지수(exponent), 가수(mantissa) 세 부분으로 나눠 저장한다. 32비트 단정밀도(single precision) 기준:
| 부호(1비트) | 지수(8비트) | 가수(23비트) |이 방식 때문에 0.1 같은 값은 이진수로 정확히 표현되지 않고 근사값으로 저장된다. 0.1 + 0.2 !== 0.3 문제가 여기서 발생한다.
4. 코드 예제
# 진법 변환과 비트 연산 예제
# 1. 십진수 -> 이진수 문자열
n = 13
print(bin(n)) # '0b1101'
print(format(n, '08b')) # '00001101' (8비트로 패딩)
# 2. 이진수 문자열 -> 십진수
print(int('1101', 2)) # 13
# 3. 2의 보수 확인 (Python은 무한 정밀도라 직접 마스킹 필요)
def to_twos_complement(value: int, bits: int = 8) -> str:
if value < 0:
value = (1 << bits) + value # 2^bits 를 더해 보수 계산
return format(value, f'0{bits}b')
print(to_twos_complement(-5, 8)) # '11111011'
# 4. 비트 연산으로 플래그(permission) 관리
READ, WRITE, EXEC = 0b001, 0b010, 0b100
permission = READ | WRITE # 읽기+쓰기 권한 부여
print(format(permission, '03b')) # '011'
has_write = bool(permission & WRITE)
print(has_write) # True
permission &= ~EXEC # 실행 권한 제거 (변화 없음, 원래 없었음)
print(format(permission & 0b111, '03b')) # '011'
# 5. IEEE 754 부동소수점 오차 확인
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False// TypeScript에서의 비트 연산과 부동소수점 오차
// 1. 비트 플래그로 사용자 권한 관리
enum Permission {
Read = 0b001,
Write = 0b010,
Exec = 0b100,
}
let userPermission = Permission.Read | Permission.Write;
console.log(userPermission.toString(2)); // '11'
const canWrite = (userPermission & Permission.Write) !== 0;
console.log(canWrite); // true
// 2. 부호 없는 우측 시프트(>>>)와 부호 있는 시프트(>>)의 차이
const negative = -8;
console.log(negative >> 1); // -4 (부호 유지)
console.log(negative >>> 1); // 2147483644 (부호 비트까지 이동)
// 3. 부동소수점 오차 대응: 정수 단위로 변환해 계산
function addMoney(a: number, b: number): number {
const cents = Math.round(a * 100) + Math.round(b * 100);
return cents / 100;
}
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(addMoney(0.1, 0.2)); // 0.35. 시간 복잡도 또는 성능 특성
- 비트 연산(AND, OR, XOR, NOT, SHIFT)은 CPU에서 1클럭 사이클 내외로 처리되는 가장 빠른 연산군에 속한다. 곱셈이나 나눗셈보다 훨씬 빠르다.
- 시프트 연산(
<<,>>)으로 2의 거듭제곱 곱셈/나눗셈을 대체하면 일반 곱셈·나눗셈 연산보다 빠르게 처리할 수 있다. 예:n << 1은n * 2와 동일하지만 더 빠르다. - 다만 현대 컴파일러(GCC, Clang, V8 등)는 이런 최적화를 자동으로 수행하는 경우가 많아서, 가독성을 희생하면서까지 수동으로 시프트 연산을 쓰는 것이 항상 이득은 아니다.
6. 실무 사용 사례
- 권한/플래그 관리: 리눅스 파일 권한(
rwx), HTTP 상태 플래그, 게임 캐릭터 상태(스턴, 무적 등)를 비트마스크(bitmask)로 압축 저장. - 네트워크 프로토콜: IP 주소의 서브넷 마스크 계산, TCP 헤더의 플래그 비트(SYN, ACK, FIN) 파싱.
- 해시/체크섬: XOR 연산을 이용한 간단한 패리티 비트(parity bit) 계산, CRC 체크섬.
- 부동소수점 오차 방지: 금융 시스템에서는 실수 대신 정수(예: 원 단위가 아닌 '전' 단위)로 금액을 저장해 IEEE 754 오차를 회피한다.
- 데이터 압축과 인코딩: Base64, UTF-8 같은 인코딩 방식도 결국 바이트(byte)의 비트 조합 규칙에 기반한다.
7. 주의할 점
- 부동소수점 비교:
0.1 + 0.2 === 0.3처럼 실수를==,===로 직접 비교하지 말고, 허용 오차(epsilon) 범위 내 비교를 사용해야 한다. - 정수 오버플로우: 언어별로 정수 크기가 다르다. JavaScript의
Number는2^53 - 1(Number.MAX_SAFE_INTEGER)까지만 정수 정밀도를 보장하고, 그 이상은BigInt를 써야 한다. - 부호 있는 시프트와 부호 없는 시프트 혼동: TypeScript/JavaScript에서
>>는 부호를 유지하고>>>는 부호 비트까지 밀어버린다. 음수 처리 시 반드시 구분해야 한다. - 엔디언(endianness) 차이: 멀티바이트 데이터를 저장할 때 빅 엔디언(big-endian)과 리틀 엔디언(little-endian) 순서가 시스템마다 다를 수 있어, 네트워크 통신이나 파일 포맷 설계 시 명시적으로 규약을 정해야 한다.
8. 핵심 정리
컴퓨터는 물리적 안정성 때문에 모든 데이터를 0과 1의 이진수로 표현하며, 정수는 2의 보수, 실수는 IEEE 754 표준을 따른다. 이 표현 방식을 이해하면 오버플로우, 부동소수점 오차, 비트 연산 최적화 같은 실무 버그의 근본 원인을 정확히 진단할 수 있고, 비트마스크 같은 기법으로 메모리와 연산 성능을 함께 개선할 수 있다.