출시·고도화 중
비트 연산 안내서 · 1/6
비트 연산(bit manipulation)은 정수를 0과 1의 나열로 보고, 그 자리 하나하나를 직접 읽고 바꾸는 기법입니다. CPU는 정수를 원래 이진수로 저장하므로 AND, OR, XOR, 시프트 같은 연산은 대개 명령어 하나로 끝납니다. 그래서 비트 연산은 빠르고 메모리를 적게 쓰며, 집합이나 플래그처럼 "있다/없다"를 여러 개 묶어 다루는 문제를 아주 짧은 코드로 표현하게 해 줍니다. 이 장에서는 이진 표현, 2의 보수, 기본 연산자와 자주 쓰는 용어를 정리합니다.
정수 13은 이진수로 1101입니다. 오른쪽 끝 자리가 0번 비트이고 왼쪽으로 갈수록 번호가 커집니다. i번 비트의 자리값은 2^i이므로 1101은 8 + 4 + 0 + 1 = 13입니다.
| 비트 번호 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|
| 자리값 | 8 | 4 | 2 | 1 |
| 13의 비트 | 1 | 1 | 0 | 1 |
가장 오른쪽 비트를 최하위 비트(LSB), 가장 왼쪽의 1을 최상위 비트(MSB)라고 부릅니다. Python에서는 bin(), format(), int(문자열, 2)로 이진 표현을 오갈 수 있습니다.
print(bin(13)) # 0b1101
print(format(13, "08b")) # 00001101 (8자리로 맞춤)
print(int("1101", 2)) # 13
print((13).bit_length()) # 4: 필요한 비트 수컴퓨터는 음수를 대부분 2의 보수(two's complement)로 저장합니다. n비트 정수에서 -x는 x의 모든 비트를 뒤집고 1을 더한 값, 즉 ~x + 1입니다. 8비트라면 5는 00000101, -5는 11111011입니다. 이렇게 하면 덧셈 회로 하나로 양수와 음수를 함께 계산할 수 있고, 0의 표현도 하나뿐입니다. n비트 부호 있는 정수의 범위는 -2^(n-1)부터 2^(n-1) - 1까지라서 32비트 int는 -2147483648부터 2147483647까지입니다. 최상위 비트가 1이면 음수로 읽힙니다.
| 연산 | 기호 | 결과 비트가 1이 되는 경우 | 예(12와 10) |
|---|---|---|---|
| AND | & | 두 비트가 모두 1 | 1100 & 1010 = 1000 (8) |
| OR | 세로 막대 | 둘 중 하나라도 1 | 1100, 1010 → 1110 (14) |
| XOR | ^ | 두 비트가 서로 다름 | 1100 ^ 1010 = 0110 (6) |
| NOT | ~ | 비트를 모두 뒤집음 | ~12 = -13 |
| 왼쪽 시프트 | << | 비트를 왼쪽으로 밀고 0을 채움 | 3 << 2 = 12 |
| 오른쪽 시프트 | >> | 비트를 오른쪽으로 밂 | 12 >> 2 = 3 |
x << k는 x * 2^k와 같고, 양수에서 x >> k는 x // 2^k와 같습니다. NOT은 2의 보수 때문에 ~x == -x - 1이 됩니다.
a, b = 12, 10
print(a & b, a | b, a ^ b) # 8 14 6
print(~a) # -13
print(3 << 2, 12 >> 2) # 12 3
print(-12 >> 1) # -6: 부호를 유지하는 산술 시프트i번 비트를 다루는 네 가지 기본 동작은 모두 마스크 1 << i로 만듭니다.
x | (1 << i)x & ~(1 << i)x ^ (1 << i)(x >> i) & 1 또는 (x & (1 << i)) != 0x = 0b1010
print(bin(x | (1 << 0))) # 0b1011
print(bin(x & ~(1 << 1))) # 0b1000
print(bin(x ^ (1 << 3))) # 0b10
print((x >> 3) & 1) # 1x & (2^k - 1))을 빠르게 할 때x & -x로 얻습니다.같은 식이라도 언어에 따라 결과가 달라질 수 있습니다. Python 정수는 크기 제한이 없어 1 << 100도 정확하고, 음수는 왼쪽으로 1이 끝없이 이어진 것처럼 다뤄집니다. JavaScript는 비트 연산 전에 숫자를 32비트 부호 있는 정수로 바꾸므로 1 << 31이 음수가 되고, 부호 없이 읽으려면 >>> 0을 씁니다. Java도 int가 32비트라 같은 일이 생기며 >>>로 0을 채우는 시프트를 합니다. C++에서는 부호 없는 타입을 써야 오버플로와 정의되지 않은 동작을 피할 수 있습니다. 자세한 내용은 구현 장과 실무 활용 장에서 다룹니다.
비트 연산은 정수를 비트의 배열로 보고 AND, OR, XOR, NOT, 시프트로 다룹니다. 음수는 2의 보수로 저장되므로 -x == ~x + 1이 성립합니다. 비트 하나를 켜고 끄고 뒤집고 확인하는 네 동작과 마스크라는 생각만 익히면, 이후 장의 부분집합 열거와 비트마스크 DP를 자연스럽게 이해할 수 있습니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.