본문으로 건너뛰기
Advertisement

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
11not
12and
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

비트 연산자와 특수 연산자를 마스터했습니다. 다음 챕터에서는 파이썬 문자열의 강력한 기능을 깊이 있게 살펴보겠습니다.

Advertisement