Ch 3.2 비트 연산자와 특수 연산자
비트 연산자는 정수의 이진 표현을 직접 조작합니다. 시스템 프로그래밍, 암호화, 플래그 관리, 성능 최적화에 자주 사용됩니다.
1. 비트 연산자
a = 0b1100 # 12 (이진수: 1100)
b = 0b1010 # 10 (이진수: 1010)
# & — 비트 AND (둘 다 1일 때 1)
print(a & b) # 8 (이진수: 1000)
print(bin(a & b)) # 0b1000
# | — 비트 OR (하나라도 1이면 1)
print(a | b) # 14 (이진수: 1110)
print(bin(a | b)) # 0b1110
# ^ — 비트 XOR (다를 때 1)
print(a ^ b) # 6 (이진수: 0110)
print(bin(a ^ b)) # 0b110
# ~ — 비트 NOT (비트 반전, 결과 = -(n+1))
print(~a) # -13 (이진수 반전 + 2의 보수)
print(~b) # -11
# << — 왼쪽 시프트 (2의 제곱만큼 곱셈 효과)
print(a << 1) # 24 (12 * 2)
print(a << 2) # 48 (12 * 4)
# >> — 오른쪽 시프트 (2의 제곱만큼 나눗셈 효과, 버림)
print(a >> 1) # 6 (12 // 2)
print(a >> 2) # 3 (12 // 4)
비트 연산 시각화
def show_bits(n: int, label: str = "") -> None:
"""정수의 비트 표현을 시각적으로 출력합니다."""
binary = format(n & 0xFF, '08b') # 8비트로 표시
print(f"{label:15} {n:4d} | {binary}")
a, b = 0b11001010, 0b10110101 # 202, 181
show_bits(a, "a")
show_bits(b, "b")
print("-" * 30)
show_bits(a & b, "a AND b")
show_bits(a | b, "a OR b")
show_bits(a ^ b, "a XOR b")
show_bits(~a & 0xFF, "NOT a (8bit)")
show_bits(a << 1 & 0xFF, "a << 1 (8bit)")
show_bits(a >> 1, "a >> 1")
2. 비트 연산 활용 예
플래그 마스킹 (Permission 시스템)
# 파일 권한 플래그 (Unix 스타일)
READ = 0b100 # 4
WRITE = 0b010 # 2
EXECUTE = 0b001 # 1
# 권한 설정
user_perm = READ | WRITE # 읽기 + 쓰기 = 0b110 = 6
# 권한 확인 — 비트 AND
print(bool(user_perm & READ)) # True (읽기 가능)
print(bool(user_perm & WRITE)) # True (쓰기 가능)
print(bool(user_perm & EXECUTE)) # False (실행 불가)
# 권한 추가 — 비트 OR
user_perm |= EXECUTE
print(bool(user_perm & EXECUTE)) # True (실행 권한 추가됨)
# 권한 제거 — AND + NOT
user_perm &= ~WRITE
print(bool(user_perm & WRITE)) # False (쓰기 권한 제거됨)
# 권한 토글 — 비트 XOR
user_perm ^= EXECUTE
print(bool(user_perm & EXECUTE)) # False (토글 — 있으면 제거)
user_perm ^= EXECUTE
print(bool(user_perm & EXECUTE)) # True (토글 — 없으면 추가)
# 실용적인 클래스로 만들기
class Permission:
READ = 1 << 2 # 4
WRITE = 1 << 1 # 2
EXECUTE = 1 << 0 # 1
def __init__(self, value: int = 0):
self._value = value
def grant(self, perm: int) -> "Permission":
return Permission(self._value | perm)
def revoke(self, perm: int) -> "Permission":
return Permission(self._value & ~perm)
def has(self, perm: int) -> bool:
return bool(self._value & perm)
def __str__(self) -> str:
r = "r" if self.has(Permission.READ) else "-"
w = "w" if self.has(Permission.WRITE) else "-"
x = "x" if self.has(Permission.EXECUTE) else "-"
return f"{r}{w}{x}"
perm = Permission()
perm = perm.grant(Permission.READ | Permission.WRITE)
print(perm) # rw-
perm = perm.grant(Permission.EXECUTE)
print(perm) # rwx
perm = perm.revoke(Permission.WRITE)
print(perm) # r-x
짝수/홀수 판별 (n & 1)
# 비트 AND로 빠른 짝수/홀수 판별
# 홀수의 최하위 비트는 항상 1
def is_odd_bitwise(n: int) -> bool:
return bool(n & 1)
def is_even_bitwise(n: int) -> bool:
return not (n & 1)
for i in range(10):
parity = "홀수" if is_odd_bitwise(i) else "짝수"
print(f"{i}: {parity}")
2의 배수 체크와 빠른 연산
# n이 2의 거듭제곱인지 확인
# 2의 거듭제곱: 이진수에서 정확히 1개의 비트만 1
def is_power_of_two(n: int) -> bool:
return n > 0 and (n & (n - 1)) == 0
test_values = [1, 2, 3, 4, 8, 16, 15, 32, 100]
for v in test_values:
print(f"{v:4d}: {is_power_of_two(v)}")
# 1: True, 2: True, 3: False, 4: True, 8: True ...
# 2의 배수로 나누기 (시프트로 빠른 나눗셈)
x = 1024
print(x >> 1) # 512 (x // 2)
print(x >> 3) # 128 (x // 8)
print(x >> 10) # 1 (x // 1024)
# 2의 배수로 곱하기 (시프트로 빠른 곱셈)
y = 7
print(y << 1) # 14 (y * 2)
print(y << 3) # 56 (y * 8)
print(y << 4) # 112 (y * 16)
3. @ 연산자 — 행렬 곱 (PEP 465)
@ 연산자는 Python 3.5+에서 도입된 행렬 곱 연산자입니다.
# numpy 없이는 @ 연산자를 내장 타입에서 직접 쓸 수 없음
# (내장 list는 지원하지 않음)
# numpy 설치: pip install numpy
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# @ 연산자로 행렬 곱
result = A @ B
print(result)
# [[19 22]
# [43 50]]
# 위와 동일 (이전 방식)
print(np.dot(A, B))
print(A.dot(B))
# 벡터 내적
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
print(v1 @ v2) # 32 (1*4 + 2*5 + 3*6)
# 커스텀 클래스에서 @ 구현
class Matrix:
def __init__(self, data: list[list[float]]):
self.data = data
self.rows = len(data)
self.cols = len(data[0])
def __matmul__(self, other: "Matrix") -> "Matrix":
"""@ 연산자 구현"""
if self.cols != other.rows:
raise ValueError("행렬 크기 불일치")
result = [[sum(self.data[i][k] * other.data[k][j]
for k in range(self.cols))
for j in range(other.cols)]
for i in range(self.rows)]
return Matrix(result)
def __str__(self) -> str:
return "\n".join(str(row) for row in self.data)
4. // 와 % 심화 — 음수에서의 동작
# 파이썬의 // 는 수학적 내림 나눗셈(Floor Division)
# 다른 언어(C, Java)의 정수 나눗셈(Truncation)과 다름
# 양수
print(7 // 2) # 3 (동일)
print(7 % 2) # 1 (동일)
# 음수 — 차이 발생!
print(-7 // 2) # -4 (파이썬: 더 작은 쪽으로 내림)
print(-7 % 2) # 1 (나머지는 항상 양수!)
# C/Java에서는: -7 / 2 = -3 (0 방향으로 자름)
# 파이썬에서는: -7 // 2 = -4 (음의 무한대 방향으로 내림)
# 검증: a == (a // b) * b + (a % b)
a, b = -7, 2
print((a // b) * b + (a % b)) # -7 ✓
# 나머지가 항상 양수인 파이썬의 특성 활용
# 원형 큐(Circular Queue) 인덱스
def circular_index(i: int, size: int) -> int:
return i % size
print(circular_index(7, 5)) # 2 (7 % 5)
print(circular_index(-1, 5)) # 4 (파이썬: -1 % 5 = 4!)
print(circular_index(-7, 5)) # 3 (-7 % 5 = 3)
# C/Java에서는 -1 % 5 = -1 이므로 다르게 처리 필요
5. 연산자 우선순위
| 우선순위 (높음→낮음) | 연산자 |
|---|---|
| 1 (가장 높음) | () 괄호 |
| 2 | ** 거듭제곱 |
| 3 | +x, -x, ~x 단항 연산 |
| 4 | *, /, //, % |
| 5 | +, - |
| 6 | <<, >> 비트 시프트 |
| 7 | & 비트 AND |
| 8 | ^ 비트 XOR |
| 9 | | 비트 OR |
| 10 | ==, !=, <, >, <=, >=, is, in |
| 11 | not |
| 12 | and |
| 13 (가장 낮음) | or |
# 우선순위 예시
print(2 + 3 * 4) # 14 (*, + 순서)
print((2 + 3) * 4) # 20 (괄호 먼저)
print(2 ** 3 ** 2) # 512 (** 는 오른쪽에서 왼쪽: 3**2=9, 2**9=512)
print(not True or False) # False (not 먼저: False or False)
print(not (True or False)) # False (괄호 먼저: True, not True = False)
# 의심스러우면 항상 괄호 사용!
result = (a & b) | (c ^ d) # 명확한 의도
6. 복합 할당 연산자 전체 정리
x = 10
x += 5 # 15 (덧셈)
x -= 3 # 12 (뺄셈)
x *= 2 # 24 (곱셈)
x /= 4 # 6.0 (나눗셈, float)
x //= 2 # 3.0 (정수 나눗셈)
x **= 3 # 27.0 (거듭제곱)
x %= 10 # 7.0 (나머지)
y = 0b1111
y &= 0b1010 # 0b1010 = 10 (비트 AND)
y |= 0b0001 # 0b1011 = 11 (비트 OR)
y ^= 0b1100 # 0b0111 = 7 (비트 XOR)
y <<= 1 # 0b1110 = 14 (왼쪽 시프트)
y >>= 2 # 0b0011 = 3 (오른쪽 시프트)
# Python 3.9+ 딕셔너리 병합 할당
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
d1 |= d2
print(d1) # {'a': 1, 'b': 3, 'c': 4}
고수 팁: 비트 조작 패턴
임시 변수 없이 스왑 (XOR 스왑):
# XOR 스왑 — 임시 변수 없이 두 정수 교환
a, b = 5, 9
print(f"교환 전: a={a}, b={b}")
a ^= b
b ^= a
a ^= b
print(f"교환 후: a={a}, b={b}")
# 교환 전: a=5, b=9
# 교환 후: a=9, b=5
# 동작 원리:
# a = a ^ b = 5 ^ 9 = 12 (0101 ^ 1001 = 1100)
# b = b ^ a = 9 ^ 12 = 5 (1001 ^ 1100 = 0101)
# a = a ^ b = 12 ^ 5 = 9 (1100 ^ 0101 = 1001)
# 단, 파이썬에서는 a, b = b, a 가 더 간결하고 안전
비트 카운팅 (popcount):
def count_set_bits(n: int) -> int:
"""1로 설정된 비트의 수를 반환합니다."""
count = 0
while n:
count += n & 1
n >>= 1
return count
# 또는 bin() 활용
def count_set_bits_v2(n: int) -> int:
return bin(n).count('1')
print(count_set_bits(255)) # 8 (11111111)
print(count_set_bits(170)) # 4 (10101010)
print(count_set_bits_v2(255)) # 8
# 실전: 해밍 거리 (두 정수의 비트 차이 수)
def hamming_distance(a: int, b: int) -> int:
return bin(a ^ b).count('1')
print(hamming_distance(1, 4)) # 2 (001 vs 100 — 2개 다름)
print(hamming_distance(93, 73)) # 2
비트 연산자와 특수 연산자를 마스터했습니다. 다음 챕터에서는 파이썬 문자열의 강력한 기능을 깊이 있게 살펴보겠습니다.