자바스크립트 비트연산 실제로 활용하기

fromzoo·2020년 12월 16일
0

자바스크립트에서 비트 연산자는
(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가 너무 크다고 느껴진다. 게다가 우리가 나중에 또 다른 속성을 추가한다면, 모델오브젝트를 두배로 늘려야 할 것이다. 이런 방식은 피해야한다.

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

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

클라이언트 사이드 코드에 약 백만개의 문장을 추가해야할 것이다. 이방법은 처음부터 오류가 발생하기 쉬운 방법이다. 그리고 후에 어떤 속성이 바뀌거나 새로운 속성이 추가됐을때, 엄청남 작업이 필요하다.

그래서 어떻게 해야할까?

비트연산자

정수 단위에서 계산하는것 대신, 수동으로 작성한 덧셈,뺄셈, 비트연산자는 각 정수를 표현한 비트위에서 작동했다. 우리에게 숫자를 직접 다루고 비교할 수 있게 만들어준다. 각각 true/false를 나타내는 속성들인 숫자의 0과 1에 따라서 4비트 (혹은 3비트나 12비트 어떤 비트든)의 숫자들을 다루기 위해 그것들을 사용할 수 있다.

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

(1).toString(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인 것이라는 것을 쉽게 알 수 있다. 10000은 오직 네번째 값만 true인 것을 의미한다. (바이너리 카운트는 오른쪽에서 왼쪽으로 간다는 것을 명심하자. 첫번째 속성이 ture인 경우 1이나 0001이된다. 네번째 속성이 1인 경우가 1000이다.)

비트연산 비교에서 가장 중요한 것은 '&'와 '|'이다. '&&'와 '||'와 형태가 매우 닮은 것은 의도적이다. '&'은 비교하는 두개의 숫자가 교집합임을 표기하는 것이다. '|'은 합집합을 의미한다. 1010 & 1001을 하게되면 1000이 리턴된다. 왜냐하면 왼쪽 끝에 있는 1이 유일하게 공통된 1이기 때문이다. 1010 | 10011011을 반환할 것이다 왜냐하면 OR 연산은 둘 중 하나만 있어도 값이 성립하기 떄문이다.

예제를 보자.

// 체크할 오브젝트를 정의해보자.
// API 결과나 유저와 상호작용할 때나, form형식에서 불러질 수 있다.
// 아마 사전엔 알 수 없는 값일 것이다. 
const myObject = {
  foo1: false,
  foo2: true,
  foo3: false,
  foo4: true
}
// 코드를 더욱 이해하기 쉽게 만들 수 있는 상수를 정의하자.
// 이 상수들은 많은 방법으로 작성될 수 있다.
// 하지만 이 방법이 직관적으로 이해하기 가장 좋은 방법이다.
const HAS_FOO1 = 1; // 0001
const HAS_FOO2 = 1 << 1; // 0010
const HAS_FOO3 = 1 << 2; // 0100
const HAS_FOO4 = 1 << 3; // 1000

// 비트연산 숫자를 만들자. 아마 use-case에 따라 다르게 만들어질 것이다.
// 하지만 해야 할 일은 같다.
// 오브젝트 키를 수동으로 체크하고 if문을 사용하여 한 번에 하나씩 속성을 추가하는 것이다.
let myBitNumber = 0;

if (myObject['foo1'] === true)
  myBitNumber = myBitNumber | HAS_FOO1;
  // 합집합의 형태를 띄기 위해 bit연산자인 "|"를 사용합니다.

if (myObject['foo2'] === true)
  myBitNumber = myBitNumber | HAS_FOO2;
  // 합집합의 형태를 띄기 위해 bit연산자인 '|'를 사용한다.

if (myObject['foo3'] === true)
  myBitNumber = myBitNumber | HAS_FOO3;

if (myObject['foo4'] === true)
  myBitNumber = myBitNumber | HAS_FOO4;

console.log(myBitNumber.toString(2));
// 1010

/*
 * 비트연산 숫자는 이제 '1010'이라는 값을 가진다.
 * 왜냐하면 두번째 값과 네번째 값이 true이기 때문이다.
 * 이렇게 표현할 수도 있다.

 * | fourth | third | second | first | <= Attribute
 * |    1   |   0   |   1    |   0   | <= True/false

*/

이제 테스트 해보자.
속성에 대한 비트연산 숫자를 체크하고 있다면, 체크할 수 있는 상태가 4가지가 있다.
1. 우리의 숫자가 하나의 명확한 속성을 가지고 있든 아니든
2. 어떤 주어진 속성의 배열을 가지고 있든 아니든,
3. 명시된 속성들만 가지고 있든 아니든
4. 속성의 배열을 전부 가지고 있든 아니든말이다.

// 위의 소스에서 myBitNumber는 1010이 나오니 참고

// 비트 숫자가 하나의 소속성만 가지고 있는지 테스트해보자.
// &는 두 숫자 사이의 교집합을 보증한다.
if (myBitNumber & HAS_FOO1) {
  // False, 이 예제에서는 False
}
if (myBitNumber & HAS_FOO2) {
  // True!
}


if (myBitNumber & (HAS_FOO1 | HAS_FOO2)) {
  // True! 1010 & 0011 의 결과로 0010이 나와서 true이다.
}
if (myBitNumber & (HAS_FOO1 | HAS_FOO3)) {
  // False
}

// 오직 명시된 속성만을 가지고 있는지 테스트
if (myBitNumber == (HAS_FOO2 | HAS_FOO4)) {
  // True
}
if (myBitNumber == (HAS_FOO2 | HAS_FOO3 | HAS_FOO4)) {
  // False
}


if (myBitNumber == (myBitNumber | (HAS_FOO2 | HAS_FOO4))) {
  // True
}
if (myBitNumber == (myBitNumber | (HAS_FOO2 | HAS_FOO3 | HAS_FOO4))) {
  // False
}

앞의 소스는 우리가 배운것의 간단한 요약이다. 여러true/false속성을 효율적으로 저장하고 비교하기 위해 비트연산자들을 사용한 함수의 예제이다.

🔍 출처블로그
자바스크립트 개발자라면 알아야 할 33가지 개념 #12 자바스크립트 비트연산 실제로 활용하기!

profile
프론트엔드 주니어 개발자 🚀

0개의 댓글