비트 연산자는 숫자의 비트를 직접 조작합니다.
C 같은 저수준 언어를 다뤄 본 경험이 없거나, 컴퓨터가 내부적으로 숫자를 어떻게 저장하는지 배운 적이 없다면 비트 연산자를 이해하기가 힘들 겁니다.
비트 연산자에 대해 알고 싶다면 컴퓨터가 숫자를 저장하는 방법에 대해 먼저 알아둬야 합니다.
하지만 비트 연산자가 꼭 필요한 경우는 거의 없으므로, 비트 연산자에 대한 이 섹션은 건너뛰거나 훑어보고 넘어가도 상관없습니다.
비트 연산자는 피연산자를 2의 보수(two’s complement) 형식으로 저장된 32 비트 부호 붙은 정수(signed integer) 로 간주합니다.
자바스크립트의 숫자는 모두 더블 형식이므로 자바스크립트는 비트 연산자를 실행하기 전에 숫자를 먼저 32 비트 정수로 변환하고, 결과를 반환할 때 다시 더블 형식으로 변환합니다.
비트 연산자는 논리 연산자 AND와 OR, NOT, XOR 같은 논리 연산을 하지만 이 연산을 정수의 개별 비트에서 수행합니다.
이외에도 비트를 다른 위치로 옮기는 시프트(shift) 연산자도 있습니다.
표 5-7 비트 연산자
연산자 | 설명 | 예제 |
---|---|---|
& | 비트 AND | 0b1010 & 0b1100 // 결과 : 0b1000 |
| | 비트 OR | 0b1010 | 0b1100 // 결과 : 0b1110 |
^ | 비트 XOR | 0b1010 ^ 0b1100 // 결과 : 0b0110 |
~ | 비트 NOT | ~0b1010 // 결과 : 0b0101 |
다음은 책 내용과 무관하게 비트연산자를 공부하면서 공부한 내용입니다.
< < | 왼쪽 시프트 | 0b1010 < < 1 // 결과 : 0b10100 0b1010 « 2 // 결과: 0b101000 |
> > | 부호가 따라가는(Sign-propagating) 오른쪽 시프트 | 아래 코드를 보십시오 |
> > > | 0으로 채우는(Zero-fit) 오른쪽 시프트 | 아래 코드를 보십시오 |
왼쪽 시프트는 2를 곱하는 효과가 있고, 오른쪽 시프트는 2로 나눈 다음 소수점 아래를 버리는 효과가 있습니다.
2의 보수 형식에서 숫자의 가장 왼쪽에 있는 비트는 음수였다면 1이고, 양수였다면 0입니다.
따라서 오른쪽 시프트는 두 가지 방법으로 할 수 있습니다.
숫자 -22를 예로 들어 봅시다.
바이너리 표현을 얻으려면 먼저 양수 22에서 시작해 1의 보수를 얻은 다음 1을 더해 2의 보수를 얻습니다.
let n = 22 // 32 비트 바이너리; 0000 0000 0000 0000 0000 0000 0001 0110(2)
n >> 1 // 0000 0000 0000 0000 0000 0000 0000 1011(2)
n >>> 1 // 0000 0000 0000 0000 0000 0000 0000 1011(2)
n = -n // 1의 보수; 1111 1111 1111 1111 1111 1111 1110 1001(2)
n++ // 2의 보수; 1111 1111 1111 1111 1111 1111 1110 1010(2)
n >> 1 // 1111 1111 1111 1111 1111 1111 1111 0101(2)
n >>> 1 // 0111 1111 1111 1111 1111 1111 1111 0101(2)
하드웨어를 직접 다루거나 컴퓨터가 내부적으로 숫자를 다루는 방법에 익숙하지 않다면 비트 연산자를 사용할 일은 별로 없을 겁니다.
하드웨어 조작을 제외하고 비트 연산자를 쓰는 것이 효율적인 경우는 플래그(불리언 값)를 다룰 때입니다.
유닉스에서는 파일에 읽기, 쓰기, 실행 권한을 각각 지정할 수 있습니다.
사용자마다 이들 권한이 각각 주어질 수 있으므로 플래그를 사용하는 것이 어울립니다.
플래그가 세 개 있으므로 이 정보를 저장하는 데는 비트 세 개가 필요합니다.
const FLAG_EXECUTE = 1 // 0b001
const FLAG_WRITE = 2 // 0b010
const FLAG_READ = 4 // 0b100
비트 연산자를 쓰면 숫자형 값 하나로 이들 플래그를 결합하고, 바꾸고, 읽을 수 있습니다.
let p = FLAG_READ | FLAG_WRITE; // 0b110
let hasWrite = p & FLAG_WRITE; // 0b010 - 참 같은 값
let hasExecute = p & FLAG_EXECUTE; // 0b000 - 거짓 같은 값
p = p ^ FLAG_WRITE; // 0b100 -- 쓰기 플래그로 토글 (이제 쓰기 권한이 없습니다)
p = p ^ FLAG_WRITE; // 0b110 -- 쓰기 플래그 토글 (쓰기 권한이 다시 생겼습니다)
// 표현식 하나로 여러 플래그를 동시에 판단할 수도 있습니다.
const hasReadOrExecute = p & (FLAG_READ | FLAG_EXECUTE);
const hasReadAndExecute = p & (FLAG_READ | FLAG_EXECUTE) === FLAG_READ | FLAG_EXECUTE;
그런데 0b001 이렇게 쓰는데 여기서 b가 뭐지..?
일반적으로 이진법의 수를 십진법의 수와 구별하기 위해 다음과 같은 방법을 쓴다.
100101b (b를 덧붙입(b는 binary의 약자)) (binary란 이진법이라는 뜻) 100101(2) ((2)를 덧붙임, 주로 수학에서 쓰임) 0b100101 (앞에 0b를 덧붙입, 주로 프로그래밍 언어에서 쓰임)
hasReadOrExecute와 hasReadAndExcute 연산자는 그룹으로 묶어야 합니다.
AND는 OR 보다 우선순위가 높으므로,
OR 연산이 AND 연산보다 먼저 일어나게 하려면 그룹을 써서 우선순위를 강제해야 합니다.