비트 연산자는 말 그대로 0, 1로 구성된 비트를 피 연산자로 하는 연산자로 &, |, ~, ^, <<, >>, >>> 7가지가 있다.
이중 <<, >>, >>>를 이동 연산자(shift operator)라고 하고 나머지 &, |, ~, ^를 논리연산자라고 한다.
&, |, !의 경우 피연산자가 boolean 타입을 가질 수 있는데 이들은 단지 true/false만을 판단하며 비트 연산자와 성격이 다르다. true & false, !true 등
이동연산자(shift operator)
쉬프트 연산자는 <<와 >>가 있으며 각각 전체 비트를 왼쪽 또는 오른쪽으로 이동시킨다. 이때 밀리는 칸은 없어지고 추가되는 칸은 0으로 채워진다.
각 비트는 0/1의 두 가지로 구성되어있기 때문에 왼쪽으로 밀면 0/1의 두 가지 경우가 생겨서 *2의 효과가, 반대는 /2의 효과가 상긴다.
이 연산은 컴퓨터에 저장된 원형의 자료를 그대로 사용하기 때문에 *2, /2를 할 때 가장 빠른 속도를 낼 수 있다.
// 쉬프트 연산자
public static void bitShift() {
int i = 100;
System.out.printf("%4d - %10s%n", i, Integer.toBinaryString(i));
// 왼쪽으로 밀 때 *2의 효과가 발생한다.
System.out.printf("%4d - %10s : %d%n", i, Integer.toBinaryString(i << 1), i << 1);
System.out.printf("%4d - %10s : %d%n", i, Integer.toBinaryString(i << 2), i << 2);
// 오른쪽으로 밀면 반대로 /2의 효과가 발생한다.
System.out.printf("%4d - %10s : %d%n", i, Integer.toBinaryString(i >> 1), i >> 1);
System.out.printf("%4d - %10s : %d%n", i, Integer.toBinaryString(i >> 2), i >> 2);
}
이외에도 >>> 연산자가 있지만 거의 사용되지 않는다.
비트 논리연산자
논리 연산자는 &, |, ~, ^ 4가지가 사용된다.
연산자
기능
&
두 피 연산 비트 값이 모두1인 경우만1,나머지는0ex) a & b
|
두 피 연산 비트 값이 모두0인 경우만0,나머지는1ex) a | b
^
두 피 연산 비트 값이 서로 다르면1,같으면0ex) a ^ b
~
피연산자의 모든비트를반전시킴. a에 대한 1의 보수ex) ~a
public static void bitLogical() {
int i = 100;
int j = 200;
System.out.printf("%6d - %32s%n", i, Integer.toBinaryString(i));
System.out.printf("%6d - %32s%n", j, Integer.toBinaryString(j));
int andResult = i & j; // 두 비트 모두 1이면 1
int orResult = i | j; // 두 비트 중 하나라도 1이면 1
int xorResult = i ^ j; // 두 비트가 서로 다르면 1
int notResult = ~i; // 기존 비트들을 모두 반전
System.out.printf("%6s - %32s%n", "i & j", Integer.toBinaryString(andResult));
System.out.printf("%6s - %32s%n", "i | j", Integer.toBinaryString(orResult));
System.out.printf("%6s - %32s%n", "i ^ j", Integer.toBinaryString(xorResult));
System.out.printf("%6s - %32s%n", "~i", Integer.toBinaryString(notResult));
}
bitmasking
배열을 이용한 논리값 관리
위의 두 가지 연산을 이용해서 비트마스킹 연산이라는 것을 할 수 있다.bitmasking이란 0/1로 구성된 bit를 마치 false/true로 간주해서 처리하는 기법이다.
예를 들어 집에 있는 32개의 전자제품에 대한 on/off 상태를 관리한다고 생각해보자. 무엇이 필요할까?
설마 32개의 변수를 선언한다고 하는 사람은 없을 것이고 가장 쉽게 떠오릴 수 있는 것은 boolean 타입의 배열이다. 아래는 1번 제품이 tv의 on/off 상태를 관리하는 모습을 배열로 작성한 예이다.
static void byArray() {
boolean[] status = new boolean[9];
int product = tv; // 1
// TV 상태 확인
System.out.printf("제품이 켜져있나? %b%n", status[product]);
// TV를 켜보자.
status[tv] = true;
System.out.printf("제품이 켜졌나? %b%n", status[product]);
// TV를 꺼보자.
status[tv] = false;
System.out.printf("제품이 꺼졌나? %b%n", status[product]);
// TV의 상태를 반전시켜보자.
status[tv] = !status[tv];
System.out.printf("제품이 토글되었나? %b%n", status[product]);
// 이제 radio를 관리해볼까?
product = radio;
}
status라는 배열에 제품의 상태를 저장하고 index를 이용해서 제품을 찾아가며 boolean 값 기반으로 관리하는 모습을 볼 수 있다.
이제 이 과정을 bitmasking으로 처리해보자. on/off를 저장할 수 있는 bit 32개를 가진 int는 이미 훌륭한 논리값 저장의 도구다(long은 무려 64개!!)
그런데 문제는 사용하는 방법이 배열과는 많이 다르다는 점이다.
bitmasking
다음은 boolean 배열을 만들었을 때와 int를 이용한 bitmasking을 활용한 예이다.