by leonardomso & jakeseo_me # #

자바스크립트에서 비트연산자는 우리에게 이상한 야생의 세계를 소개합니다. (12 & 3) = 0이 되고 (12 & 4) = 4가 되는 세상이죠. 진짜입니다. 콘솔에서 지금 당장 실행해보세요. 저는 거짓말 안합니다! 정확히 어떻게 동작하는지 모르신다면, 계속 읽어보세요. 때때로 비트연산자는 우리가 어떻게 해결해야 할지 확신이 없는 문제들을 해결해주는 해결사가 될 수도 있습니다!

최근에 팀 동료가 우리 팀 전체에게 오브젝트 안에 있는 4개의 독립적인 true/false 변수들의 존재를 체크하고 저장하는 가장 좋은 방법이 무엇인가에 대해 물었습니다. 이 속성들을 foo1부터 foo4라고 불러봅시다. JavaScript(ES6)에서의 표현법은 아마 다음과 같을 것입니다.

const myObject = {
  foo1: false,
  foo2: true,
  foo3: false,
  foo4: true
};

상당히 직관적입니다. 하지만, 우리의 어플리케이션은 이 속성들의 아주 아주 많은 조합을 체크해야될 필요가 있습니다. 어렵기도 하고, 언젠가는 하나의 추가적인 속성을 더 추가해야 할 수도 있습니다. 이 문제를 해결하기 위해서 두가지 가장 확실한 옵션이 있었습니다.

1) 모든 가능한 모델 오브젝트 만들기, 그리고 필요할 때마다 코드를 비교하기

const hasFoo2andFoo4 = {
  foo1: false,
  foo2: true,
  foo3: false,
  foo4: true
}

const hasFoo3andFoo4 = {
  foo1: false,
  foo2: false,
  foo3: true,
  foo4: true
}

// ... 나머지 경우의 수 ...

// 그 후에
if (isEqual(myObject, hasFoo2andFoo4)) {
  // 오브젝트가 Foo2와 Foo4만 가지고 있는지 알 수 있습니다.
}

보이는 그대로, 이 방법은 구립니다. 비교하기 위해 16개의 모델 오브젝트를 생성해야 할 것입니다. 이러한 작업은 작은 정보를 얻기 위해서 overhead가 너무 크다고 느껴집니다. 게다가, 우리가 나중에 또 다른 속성을 추가한다면, 모델 오브젝트를 두배로 늘려야 할 것입니다. 분명히, 이런 방식은 피해야 합니다. 하지만 다른 옵션은 더 나쁠 수도 있죠...

2) 조건 블록 내에서 각각 개별 프로퍼티 체크하기

if (myObject[2] && myObject[4] && !(myObject[1] || myObject[3])) {
  // 우린 오브젝트가 Foo2와 Foo4만 갖고 있다는 것을 알 수 있습니다.
}

이 방법은 정말 생생한 악몽입니다. 클라이언트 사이드 코드에 약 백만개의 문장을 추가해야 할 것입니다. 이 방법은 처음부터 오류가 발생하기 쉬운 방법입니다. 그리고 후에 어떤 속성이 바뀌거나 새로운 속성이 추가됐을 때, 엄청난 작업이 필요할 것입니다. 그래서 우리는 어떻게 해야 할까요?

카페인에 심각하게 취해서 아는 것이라곤 하나도 없는 상태의 아침이었습니다. 저는 우리가 하는 일이 기본적으로 유닉스 파일 시스템에 쓰이는 권한 관리 비트마스킹 함수를 따라하고 있다는 것을 깨달았습니다. 이를테면, "755"는 읽기-쓰기-실행이고, "rwxr-xr-x"같은 거 있잖아요? 비록 파일 시스템 권한 관리는 더욱 복잡하지만, 아마 조금 스케일을 낮추면 우리 문제를 해결하는데 사용될 수 있을 것이라고 생각했습니다. 제 머릿속에서, 어떻게 각 속성에 번호를 부여해야 할지, 그리고 각 상태에 대해 유니크한 번호를 붙여줄지 머리털 한 올까지 집중력을 끌어올리기 시작했습니다. 그리고 ...

다른 동료 한명이 제 자신으로부터 저를 구해줬습니다. "파일 시스템 권한 관리를 재발명하는것 보다는, 그냥 비트 연산자를 쓰는 게 어때?" 그가 말했습니다. "그래 좋아". 저는 흐릿한 아이디어가 있었습니다. 저는 ~ 연산자를 지난 몇년간 단 몇번밖에 써보지 않았습니다. 그래서 비트 연산자에 대한 완벽한 이해가 없었습니다. 운좋게도, 이 동료 개발자는 지난 프로젝트에서 비트 연산자를 써봤고 우리의 두뇌를 깨울 수 있었습니다.

파일 시스템 권한 관리지만 제가 상상했던 것보다 훨씬 우아했습니다. 정수단위에서 계산하는 것 대신, 수동으로 작성한 덧셈, 뺄셈, 비트 연산자는 각 정수를 표현한 비트 위에서 작동했습니다. 우리에게 숫자를 직접 다루고 비교할 수 있게 만들어주었습니다. 각각 true/false를 나타내는 속성들인 숫자의 0과 1에 따라서 4비트(혹은 3비트나 12비트 어떤 비트든)의 숫자들을 다루기 위해 그것들을 사용할 수 있었습니다.

자바스크립트 내부의 모든 정수들(64bit/9,007,199,254,740,991까지의 수)은 2진법으로 표기될 수 있습니다. toString(2)를 호출함으로써 그들이 어떻게 변하는지 보세요.

(1).toStirng(2);
// 1

(2).toString(2);
// 10

(3).toString(2);
// 11

(4).toString(2);
// 100

// ...

(3877494).toString(2);
// 1110110010101001110110

이제 이론을 알았습니다. 이제 중요한 부분으로 가봅시다. 이 방법에 숨어있는 진짜 트릭은 비트연산은 이 바이너리 문자열들을 직접 다루고 비교할 수 있게 해준다는 것입니다. 바이너리 문자열 오른쪽에 0를 넣어주는 << 비트연산은 우리의 10진법 정수를 2진법의 규칙에 맞게 증가시킵니다. 다음 코드를 보세요.

// `fooBar`를 숫자 2로 셋팅해봅시다.

fooBar.toString(2);
// 10 <- 2의 2진법 표기입니다.

// fooBar의 바이너리 값의 끝에 0을 삽입할 것입니다.
// 표기법은 다음과 같습니다.
foobar = fooBar << 1;

fooBar.toString(2);
// 100

// ... 이제 4가 됐습니다.
console.log(fooBar);
// 4

이제 어떻게 동작하는지 알았을 것입니다. 비트연산 전반을 고려해서, 이제 이진법에서 더하거나 빼거나 비교할 수 있습니다. 위의 상세한 예제에서, 단일 4비트 숫자 내부에 우리는 4개의 가능한 속성을 저장할 수 있습니다. 0000-1111 사이에 말입니다. 각각의 비트는 true일 때 (1)로, false일 때 (0)으로 표기될 수 있겠죠. 이러한 규칙을 이용해서 1111은 4개의 모든 속성이 true인 것이라는 것을 쉽게 상상할 수 있겠죠? 1000은 오직 네번째 값만 true인 것을 의미합니다. (바이너리 카운트는 오른쪽에서 왼쪽으로 간다는 것을 항상 명심해두세요. 첫번째 속성이 true인 경우 1이나 0001이 됩니다. 네번째 속성이 1인 경우가 1000이고요.)