[CS 기초 #3] 이진수와 논리 게이트: 컴퓨터가 0과 1로 생각하는 방법
컴퓨터의 언어를 배워봅시다! 모든 프로그램, 모든 데이터는 결국 0과 1로 이루어져 있습니다. 이진수와 논리 게이트를 이해하면 컴퓨터의 마법이 과학으로 보입니다.
🎯 이 글을 읽고 나면
- 컴퓨터가 왜 이진수(0과 1)를 사용하는지 설명할 수 있습니다
- 10진수, 2진수, 16진수 간의 변환을 할 수 있습니다
- AND, OR, NOT, XOR 같은 논리 게이트의 동작을 이해합니다
- 논리 게이트로 덧셈기를 만드는 원리를 알게 됩니다
- 비트 연산을 실무에 활용할 수 있습니다
📚 사전 지식
- 기본 산술: 10진수 덧셈, 곱셈만 알면 됩니다
- 프로그래밍 기초: 변수, 조건문 정도 (Python 예제가 있지만 필수는 아님)
- 컴퓨터 구조 기본: 이전 글: 컴퓨터 구조를 읽으면 더 좋습니다
💡 핵심 개념 미리보기
컴퓨터는 전기 신호의 ON(1)과 OFF(0) 두 가지 상태만 구분할 수 있어 이진수를 사용합니다. 논리 게이트는 이 0과 1을 입력받아 간단한 연산(AND, OR, NOT 등)을 수행하는 기본 부품이며, 이들을 조합하면 덧셈기, 곱셈기 등 모든 연산을 구현할 수 있습니다. 실무에서는 비트 연산을 활용해 권한 관리, 암호화, 네트워크 프로토콜 등을 효율적으로 처리합니다!
🔢 들어가며: 왜 컴퓨터는 0과 1만 아는가?
“Hello World”라는 문자열이 어떻게 0과 1의 조합으로 변환되고, 간단한 덧셈이 어떻게 논리 게이트를 통해 계산되는지 궁금하신가요?
오늘은 컴퓨터의 가장 기본적인 언어인 이진수와 모든 연산의 기초가 되는 논리 게이트를 알아보겠습니다. 이 지식은 비트 연산, 암호화, 네트워크 프로토콜 이해의 기초가 됩니다!
🎯 왜 이진수를 사용하는가?
물리적 이유
전자 회로에서 두 가지 상태를 구분하기 가장 쉽습니다:
- HIGH (5V) = 1
- LOW (0V) = 0
graph LR
subgraph "전압 레벨"
V5[5V - HIGH - 1]
V0[0V - LOW - 0]
end
subgraph "노이즈 마진"
H[3.5V ~ 5V = 1]
L[0V ~ 1.5V = 0]
N[1.5V ~ 3.5V = 불확실]
end
style V5 fill:#ff9999
style V0 fill:#9999ff
style N fill:#ffff99
📊 진법 체계의 이해
🔰 초보자를 위한 비유
이진수를 전등 스위치에 비유해봅시다!
- 10진수: 손가락 10개로 세는 것처럼, 0~9까지 10개 숫자 사용
- 2진수: 전등 스위치처럼, 꺼짐(0)과 켜짐(1) 두 가지만 사용
- 16진수: 한 손으로 0~F(15)까지 표현 (0-9, A-F)
예를 들어:
- 전등 3개로 숫자 표현: 🔦🔦⚫ = 110 (2진수) = 6 (10진수)
- 켜진(1) 전등만 세기: 왼쪽부터 4, 2, 1의 자리 → 4 + 2 + 0 = 6
컴퓨터는 수백만 개의 “전등 스위치”(트랜지스터)를 가지고 모든 계산을 합니다!
10진수, 2진수, 16진수 비교
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def number_systems_demo():
"""다양한 진법 체계 시연"""
decimal = 42
# Python의 내장 변환 함수
binary = bin(decimal) # '0b101010'
hexadecimal = hex(decimal) # '0x2a'
octal = oct(decimal) # '0o52'
print(f"10진수: {decimal}")
print(f"2진수: {binary}")
print(f"16진수: {hexadecimal}")
print(f"8진수: {octal}")
# 직접 구현한 진법 변환
def to_binary(n):
if n == 0:
return "0"
result = ""
while n > 0:
result = str(n % 2) + result
n = n // 2
return result
print(f"\n직접 변환한 2진수: {to_binary(decimal)}")
# 각 자리수의 의미
print("\n2진수 101010의 각 자리 값:")
binary_str = "101010"
for i, bit in enumerate(reversed(binary_str)):
value = int(bit) * (2 ** i)
print(f"2^{i} × {bit} = {value}")
print(f"합계: {sum(int(bit) * (2**i) for i, bit in enumerate(reversed(binary_str)))}")
number_systems_demo()
진법 변환 실습
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class NumberConverter:
"""진법 변환기"""
@staticmethod
def decimal_to_binary(decimal):
"""10진수 → 2진수"""
if decimal == 0:
return "0"
result = []
while decimal > 0:
result.append(str(decimal % 2))
decimal //= 2
return ''.join(reversed(result))
@staticmethod
def binary_to_decimal(binary):
"""2진수 → 10진수"""
decimal = 0
for i, bit in enumerate(reversed(binary)):
decimal += int(bit) * (2 ** i)
return decimal
@staticmethod
def decimal_to_hex(decimal):
"""10진수 → 16진수"""
hex_chars = "0123456789ABCDEF"
if decimal == 0:
return "0"
result = []
while decimal > 0:
result.append(hex_chars[decimal % 16])
decimal //= 16
return ''.join(reversed(result))
@staticmethod
def binary_to_hex(binary):
"""2진수 → 16진수 (4비트씩 묶어서 변환)"""
# 4의 배수로 패딩
while len(binary) % 4 != 0:
binary = '0' + binary
hex_chars = "0123456789ABCDEF"
result = []
for i in range(0, len(binary), 4):
chunk = binary[i:i+4]
decimal = NumberConverter.binary_to_decimal(chunk)
result.append(hex_chars[decimal])
return ''.join(result)
# 사용 예시
converter = NumberConverter()
print("255 (10진수) =", converter.decimal_to_binary(255), "(2진수)")
print("11111111 (2진수) =", converter.binary_to_decimal("11111111"), "(10진수)")
print("255 (10진수) =", converter.decimal_to_hex(255), "(16진수)")
print("11111111 (2진수) =", converter.binary_to_hex("11111111"), "(16진수)")
🔌 논리 게이트: 컴퓨터의 기본 부품
기본 논리 게이트
graph TD
subgraph "AND 게이트"
A1[A] --> AND{AND}
B1[B] --> AND
AND --> C1[A AND B]
end
subgraph "OR 게이트"
A2[A] --> OR{OR}
B2[B] --> OR
OR --> C2[A OR B]
end
subgraph "NOT 게이트"
A3[A] --> NOT{NOT}
NOT --> C3[NOT A]
end
subgraph "XOR 게이트"
A4[A] --> XOR{XOR}
B4[B] --> XOR
XOR --> C4[A XOR B]
end
논리 게이트 진리표와 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class LogicGates:
"""기본 논리 게이트 구현"""
@staticmethod
def AND(a, b):
"""AND 게이트: 둘 다 1일 때만 1"""
return a & b
@staticmethod
def OR(a, b):
"""OR 게이트: 하나라도 1이면 1"""
return a | b
@staticmethod
def NOT(a):
"""NOT 게이트: 반전"""
return 1 - a # 또는 ~a & 1
@staticmethod
def XOR(a, b):
"""XOR 게이트: 서로 다르면 1"""
return a ^ b
@staticmethod
def NAND(a, b):
"""NAND 게이트: AND의 반전"""
return LogicGates.NOT(LogicGates.AND(a, b))
@staticmethod
def NOR(a, b):
"""NOR 게이트: OR의 반전"""
return LogicGates.NOT(LogicGates.OR(a, b))
@staticmethod
def print_truth_table():
"""진리표 출력"""
print("=" * 50)
print("논리 게이트 진리표")
print("=" * 50)
print("A | B | AND | OR | XOR | NAND | NOR")
print("-" * 50)
for a in [0, 1]:
for b in [0, 1]:
print(f"{a} | {b} | {LogicGates.AND(a,b)} | {LogicGates.OR(a,b)} | {LogicGates.XOR(a,b)} | {LogicGates.NAND(a,b)} | {LogicGates.NOR(a,b)}")
# 진리표 출력
LogicGates.print_truth_table()
🧮 논리 게이트로 만드는 연산 회로
반가산기 (Half Adder)
1비트 덧셈을 수행하는 가장 기본적인 회로
1
2
3
4
5
6
7
8
9
10
11
12
13
def half_adder(a, b):
"""반가산기: 1비트 + 1비트"""
sum_bit = a ^ b # XOR
carry = a & b # AND
return sum_bit, carry
# 반가산기 테스트
print("반가산기 진리표:")
print("A + B = Sum, Carry")
for a in [0, 1]:
for b in [0, 1]:
s, c = half_adder(a, b)
print(f"{a} + {b} = {s}, {c}")
전가산기 (Full Adder)
이전 자리에서의 캐리를 포함한 덧셈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def full_adder(a, b, carry_in):
"""전가산기: 캐리 입력을 포함한 1비트 덧셈"""
# 첫 번째 반가산기
sum1, carry1 = half_adder(a, b)
# 두 번째 반가산기
sum2, carry2 = half_adder(sum1, carry_in)
# 최종 캐리
carry_out = carry1 | carry2
return sum2, carry_out
# 전가산기 테스트
print("\n전가산기 진리표:")
print("A + B + Cin = Sum, Cout")
for a in [0, 1]:
for b in [0, 1]:
for cin in [0, 1]:
s, cout = full_adder(a, b, cin)
print(f"{a} + {b} + {cin} = {s}, {cout}")
4비트 가산기
여러 전가산기를 연결하여 다중 비트 덧셈 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def ripple_carry_adder_4bit(a4, a3, a2, a1, b4, b3, b2, b1):
"""4비트 리플 캐리 가산기"""
# 최하위 비트부터 계산
s1, c1 = half_adder(a1, b1)
s2, c2 = full_adder(a2, b2, c1)
s3, c3 = full_adder(a3, b3, c2)
s4, c4 = full_adder(a4, b4, c3)
return [s4, s3, s2, s1], c4
# 테스트: 5 + 3 = 8
# 5 = 0101, 3 = 0011
result, carry = ripple_carry_adder_4bit(0, 1, 0, 1, 0, 0, 1, 1)
print(f"\n5 + 3 = {''.join(map(str, result))} (2진수), 캐리: {carry}")
print(f"결과: {int(''.join(map(str, result)), 2)} (10진수)")
💻 비트 연산: 실무에서의 활용
비트 연산자 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class BitOperations:
"""실용적인 비트 연산 예제"""
@staticmethod
def is_even(n):
"""짝수 판별 (최하위 비트 확인)"""
return (n & 1) == 0
@staticmethod
def multiply_by_2(n):
"""2 곱하기 (왼쪽 시프트)"""
return n << 1
@staticmethod
def divide_by_2(n):
"""2 나누기 (오른쪽 시프트)"""
return n >> 1
@staticmethod
def swap_without_temp(a, b):
"""임시 변수 없이 두 값 교환"""
a = a ^ b
b = a ^ b
a = a ^ b
return a, b
@staticmethod
def set_bit(num, position):
"""특정 위치의 비트를 1로 설정"""
mask = 1 << position
return num | mask
@staticmethod
def clear_bit(num, position):
"""특정 위치의 비트를 0으로 설정"""
mask = ~(1 << position)
return num & mask
@staticmethod
def toggle_bit(num, position):
"""특정 위치의 비트 토글"""
mask = 1 << position
return num ^ mask
@staticmethod
def check_bit(num, position):
"""특정 위치의 비트 확인"""
mask = 1 << position
return (num & mask) != 0
@staticmethod
def count_set_bits(n):
"""1인 비트의 개수 세기 (Brian Kernighan's Algorithm)"""
count = 0
while n:
n &= n - 1 # 가장 오른쪽 1을 제거
count += 1
return count
# 비트 연산 테스트
bit_ops = BitOperations()
print("비트 연산 예제:")
print(f"10이 짝수인가? {bit_ops.is_even(10)}")
print(f"5 × 2 = {bit_ops.multiply_by_2(5)}")
print(f"10 ÷ 2 = {bit_ops.divide_by_2(10)}")
a, b = 5, 7
print(f"\n교환 전: a={a}, b={b}")
a, b = bit_ops.swap_without_temp(a, b)
print(f"교환 후: a={a}, b={b}")
num = 0b1010 # 10
print(f"\n원본: {bin(num)}")
print(f"2번 비트 설정: {bin(bit_ops.set_bit(num, 2))}")
print(f"1번 비트 제거: {bin(bit_ops.clear_bit(num, 1))}")
print(f"0번 비트 토글: {bin(bit_ops.toggle_bit(num, 0))}")
print(f"3번 비트 확인: {bit_ops.check_bit(num, 3)}")
print(f"1인 비트 개수: {bit_ops.count_set_bits(num)}")
비트 마스크 활용: 권한 관리 시스템
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class PermissionSystem:
"""비트 마스크를 활용한 권한 관리"""
# 권한 정의 (비트 플래그)
READ = 0b0001 # 1
WRITE = 0b0010 # 2
EXECUTE = 0b0100 # 4
DELETE = 0b1000 # 8
def __init__(self):
self.users = {}
def add_user(self, username, permissions=0):
"""사용자 추가"""
self.users[username] = permissions
def grant_permission(self, username, permission):
"""권한 부여"""
if username in self.users:
self.users[username] |= permission
def revoke_permission(self, username, permission):
"""권한 회수"""
if username in self.users:
self.users[username] &= ~permission
def check_permission(self, username, permission):
"""권한 확인"""
if username in self.users:
return (self.users[username] & permission) == permission
return False
def get_permissions_string(self, username):
"""권한을 문자열로 표시"""
if username not in self.users:
return "User not found"
perms = self.users[username]
result = []
if perms & self.READ:
result.append("READ")
if perms & self.WRITE:
result.append("WRITE")
if perms & self.EXECUTE:
result.append("EXECUTE")
if perms & self.DELETE:
result.append("DELETE")
return " | ".join(result) if result else "NO PERMISSIONS"
# 권한 시스템 테스트
perm_system = PermissionSystem()
# 사용자 추가
perm_system.add_user("alice", PermissionSystem.READ | PermissionSystem.WRITE)
perm_system.add_user("bob", PermissionSystem.READ)
print("초기 권한 상태:")
print(f"Alice: {perm_system.get_permissions_string('alice')}")
print(f"Bob: {perm_system.get_permissions_string('bob')}")
# 권한 변경
perm_system.grant_permission("bob", PermissionSystem.EXECUTE)
perm_system.revoke_permission("alice", PermissionSystem.WRITE)
print("\n권한 변경 후:")
print(f"Alice: {perm_system.get_permissions_string('alice')}")
print(f"Bob: {perm_system.get_permissions_string('bob')}")
# 권한 확인
print(f"\nAlice has WRITE permission: {perm_system.check_permission('alice', PermissionSystem.WRITE)}")
print(f"Bob has EXECUTE permission: {perm_system.check_permission('bob', PermissionSystem.EXECUTE)}")
🎨 문자와 이진수: ASCII와 유니코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def character_encoding_demo():
"""문자 인코딩 시연"""
# ASCII 인코딩
text = "Hello"
print(f"텍스트: {text}")
print("ASCII 코드:")
for char in text:
ascii_code = ord(char)
binary = bin(ascii_code)[2:].zfill(8)
print(f" '{char}' = {ascii_code} (10진수) = {binary} (2진수)")
# 이진수로 문자열 표현
binary_string = ' '.join(format(ord(char), '08b') for char in text)
print(f"\n전체 이진 표현:\n{binary_string}")
# 이진수에서 문자열로 복원
binary_list = binary_string.split()
decoded = ''.join(chr(int(b, 2)) for b in binary_list)
print(f"\n복원된 텍스트: {decoded}")
# 유니코드 예제
emoji = "👍"
print(f"\n이모지: {emoji}")
print(f"유니코드 코드포인트: U+{ord(emoji):04X}")
print(f"UTF-8 바이트: {emoji.encode('utf-8').hex()}")
character_encoding_demo()
🔐 실무 응용: 체크섬과 패리티 비트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def parity_check_demo():
"""패리티 비트를 이용한 오류 검출"""
def add_even_parity(data):
"""짝수 패리티 비트 추가"""
ones_count = bin(data).count('1')
parity_bit = 1 if ones_count % 2 == 1 else 0
return (data << 1) | parity_bit
def check_even_parity(data_with_parity):
"""짝수 패리티 검증"""
ones_count = bin(data_with_parity).count('1')
return ones_count % 2 == 0
# 원본 데이터
original = 0b1101001 # 'i'의 ASCII
print(f"원본 데이터: {bin(original)}")
# 패리티 비트 추가
with_parity = add_even_parity(original)
print(f"패리티 포함: {bin(with_parity)}")
# 정상 전송
print(f"패리티 검증 (정상): {check_even_parity(with_parity)}")
# 비트 오류 발생 시뮬레이션
corrupted = with_parity ^ 0b100 # 3번째 비트 플립
print(f"손상된 데이터: {bin(corrupted)}")
print(f"패리티 검증 (오류): {check_even_parity(corrupted)}")
parity_check_demo()
🎯 핵심 정리
꼭 기억해야 할 5가지
- 이진수는 컴퓨터의 기본 언어 (전기 신호의 ON/OFF)
- 논리 게이트는 모든 연산의 기초 (AND, OR, NOT, XOR)
- 비트 연산은 매우 빠르고 효율적
- 진법 변환은 CS의 기본 소양
- 비트 마스크로 효율적인 플래그 관리 가능
실무 활용 팁
- 권한 관리나 옵션 설정에 비트 플래그 활용
- 성능이 중요한 곳에서 비트 연산 사용
- 네트워크 프로토콜 이해에 비트 레벨 지식 필수
- 암호화와 해싱 알고리즘의 기초
📚 추가 학습 자료
추천 도서
- “Code: The Hidden Language” - Charles Petzold
- “Digital Design and Computer Architecture” - Harris & Harris
실습 프로젝트
- 간단한 ALU(산술논리장치) 구현
- 비트 연산만으로 사칙연산 구현
- 오류 정정 코드(해밍 코드) 구현
🚀 다음 시간 예고
다음 포스트에서는 “운영체제 기초”를 다룹니다. 프로세스와 스레드의 차이, 메모리 관리, 스케줄링 알고리즘 등 OS의 핵심 개념을 알아보겠습니다!
✅ 이 글에서 배운 것
스스로 확인해보세요! 각 항목을 설명할 수 있다면 체크하세요.
개념 이해
- 컴퓨터가 이진수를 사용하는 이유 (전기 신호의 HIGH/LOW)
- 10진수 ↔ 2진수 ↔ 16진수 변환 원리
- AND, OR, NOT, XOR 논리 게이트의 진리표
- 반가산기와 전가산기의 차이
- 비트 마스크의 개념과 활용
실용적 이해
- 왜 16진수를 메모리 주소 표현에 사용하는지 안다
- 비트 연산이 일반 연산보다 빠른 이유를 설명할 수 있다
- 권한 관리 시스템에 비트 플래그를 쓰는 이유
- 패리티 비트로 오류를 검출하는 원리
실습 완료
- 진법 변환 코드를 직접 실행해봤다
- 논리 게이트 진리표를 출력해봤다
- 4비트 가산기로 덧셈을 시뮬레이션했다
- 비트 연산 예제들을 실행하고 이해했다
- 권한 관리 시스템 코드를 테스트해봤다
