비트 연산은 알고리즘 문제에만 나오지 않습니다. 운영 체제의 파일 권한, 네트워크 주소, 해시 테이블, 데이터베이스 인덱스, 게임 엔진까지 성능과 메모리가 중요한 곳이라면 어디에나 있습니다. 이 장에서는 실제 사례와 언어별 함정, 더 읽을거리를 정리합니다.
플래그와 권한
여러 개의 켜짐/꺼짐 옵션을 정수 하나에 담는 것이 가장 흔한 쓰임입니다. Unix 파일 권한 755는 8진수로 쓴 9개의 비트(소유자·그룹·기타 사용자 각각 rwx)입니다. Python의 enum.Flag, C#의 [Flags] 열거형, Java의 EnumSet도 내부적으로 비트를 씁니다.
import stat
from enum import Flag, auto
mode = 0o754 # rwxr-xr--
print(bool(mode & stat.S_IXUSR)) # True: 소유자 실행 가능
print(bool(mode & stat.S_IWGRP)) # False: 그룹 쓰기 불가
print(stat.filemode(stat.S_IFREG | mode)) # -rwxr-xr--
class Perm(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
user = Perm.READ | Perm.WRITE
print(Perm.WRITE in user, Perm.EXECUTE in user) # True False
user &= ~Perm.WRITE
print(user) # Perm.READ
네트워크 주소
IPv4 주소는 32비트 정수이고, 서브넷 마스크와 AND 하면 네트워크 주소가 나옵니다. 라우터와 방화벽이 패킷마다 하는 일이 바로 이 연산입니다.
import ipaddress
ip = int(ipaddress.IPv4Address("192.168.10.77"))
prefix = 24
mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
print(ipaddress.IPv4Address(ip & mask)) # 192.168.10.0
print(ipaddress.ip_interface("192.168.10.77/24").network) # 192.168.10.0/24
그 밖의 사례
- 해시 테이블: Java
HashMap은 테이블 크기를 2의 거듭제곱으로 맞추고 (n - 1) & hash로 칸을 고릅니다. 나머지 연산보다 싸기 때문입니다. - 비트셋과 비트맵 인덱스: 검색 엔진과 분석 데이터베이스는 "이 문서가 조건을 만족하는가"를 비트 하나로 저장하고 AND·OR로 조건을 합칩니다. Roaring Bitmap은 Lucene, Apache Druid, Apache Spark 등에서 쓰입니다.
- 블룸 필터: 여러 해시 값 위치의 비트를 켜 두고, 모두 켜져 있는지로 "아마 있음/확실히 없음"을 판단합니다.
- Redis:
SETBIT, GETBIT, BITCOUNT로 일별 접속 여부 같은 대량의 불리언 값을 적은 메모리로 저장합니다. - 체스 엔진의 비트보드: 64칸을 64비트 정수로 표현해 말의 이동 가능 칸을 시프트와 AND로 한 번에 계산합니다.
- 펜윅 트리:
i & -i로 다음 구간으로 이동합니다. - 그래픽: RGBA 색상을 32비트 정수에 8비트씩 담고 시프트와 마스크로 꺼냅니다.
언어별 함정
- 연산자 우선순위: C, C++, Java, JavaScript에서는
==가 &보다 먼저 계산되어 x & 1 == 0이 x & (1 == 0)으로 읽힙니다. 비트 연산에는 항상 괄호를 칩니다. - 오버플로: Java와 JavaScript의
1 << 31은 음수이고, C++의 1 << 40은 int 범위를 넘습니다. 넓은 마스크는 1L << i(Java), 1ULL << i(C++), 1n << 40n(JavaScript BigInt)로 만듭니다. - 시프트 횟수: Java와 JavaScript는 32비트 값에서 시프트 횟수의 하위 5비트만 쓰므로
1 << 32가 1입니다. C++에서는 정의되지 않은 동작입니다. - 오른쪽 시프트: Java와 JavaScript의
>>는 부호를 유지하고 >>>는 0을 채웁니다. C++는 부호 없는 타입에서 논리 시프트를 하며, 부호 있는 음수의 오른쪽 시프트는 C++20부터 산술 시프트로 정해졌습니다. - Python 음수: 왼쪽으로 1이 끝없이 이어진 것처럼 동작하므로
bin(-5)는 -0b101입니다. 32비트 결과를 흉내 내려면 & 0xFFFFFFFF로 자릅니다.
def to_int32(x: int) -> int:
x &= 0xFFFFFFFF
return x - (1 << 32) if x & 0x80000000 else x
print(to_int32(1 << 31)) # -2147483648: Java·JS의 1 << 31과 같음
print(-1 & 0xFFFFFFFF) # 4294967295: JS의 -1 >>> 0과 같음
print(to_int32(0xDEADBEEF)) # -559038737
XOR 교환(a ^= b; b ^= a; a ^= b)은 요즘 컴파일러에서 더 빠르지도 않고, 같은 변수를 가리키면 값이 0이 되므로 쓰지 않는 편이 낫습니다. 비트 트릭은 이름 있는 상수와 작은 함수로 감싸 읽는 사람을 배려하세요.
정리
비트 연산은 플래그, 권한, 네트워크 주소, 해시 테이블, 비트맵 인덱스처럼 많은 불리언 값을 빠르고 작게 다뤄야 하는 곳에 쓰입니다. 우선순위와 정수 폭, 시프트 규칙이 언어마다 다르다는 점만 조심하면, 짧고 빠른 코드를 안전하게 쓸 수 있습니다.
더 읽을거리
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.