비트 연산자는 말 그대로 비트 단위의 연산자로 AND(&),OR(|),XOR(^),NOT(~)이 있다.
비트 AND(&)는 두 값이 모두 1일 때만 1인 연산자,
비트 OR(|)은 두 값이 모두 0일 때만 0인 연산자다.
비트 XOR(^)은 두 값이 같을 때 0, 다를 때 1의 값을 지니며,
비트 NOT(~)은 0은 1, 1은 0으로 반전하는 연산자다.
두 정수 사이에 비트 연산을 수행하면 자바 가상 머신이 알아서 연산 결과를 알려 주겠지만 실제로 계산을 할 수 있어야 결과가 맞는지 알 수 있을 것이다.
정수 3과 정수 10의 비트 AND, 비트 OR, 비트 XOR 연산 과정을 살펴 보자. 비트 단위의 연산을 직접 계산하기 위해서는 10진수를 비트 단위의 표현 방식인 2진수로 바꿔 표기할 수 있어야 한다. 여기서는 정수의 진법 변환 방법을 수학적으로 다루지 않는다. 정수의 진법 변환 방법을 계산할 수 있다면 비트 간의 연산을 이애하는데 도움이 되겠지만 실제 수학적으로 변환하기 힘들더라도 자바 코드 상에서 10진수를 2진수, 8진수, 16진수로 변환하는 메서드(Interger.toBinaryString(),Integer.toOctalString(),Integer.toHexString()),와 이를 10진수로 변환하는 메서드(Integer.parseInt())**를 활용해 얼마든지 각 진법으로 표현된 수의 10진수 값을 알아 낼 수 있으므로 걱정하지 않아도 된다.
먼저 비트 AND 연산을 살펴보자. 10진수 3은 2진수 00000011이고 10진수 10은 00001010로 변환된다. 이 두 정수의 각 비트 간 AND 연산 결과는 2진수 00000010이며, 이를 10진수로 변환하면 값이 2이다. 2진수, 16진수와 같은 다양한 진법을 사용해 직접 비트 AND 연산을 수행해도 이와 동일한 결과를 얻을 수 있다.
System.out.println(3 & 10); //2
System.out.println(0b00000011 & 0b00001010); //2
System.out.println(0x03 & 0x0A); //2
+)비트 연산을 할 때 사용되는 비트 수
실제 비트 연산을 수행할 때의 최소 단위는 int(4 byte = 32bit) 이므로 10진수 3을 2진수로 변환하면 0이 30개 1이 2개인 32비트 2진수로 변환된다. 다만 앞의 0들은 연산 결과에 영향을 미치지 않으므로 생략해 표현할 수 있다. 즉 다음 코드에서 변수 a, b, c는 모두 같은 수를 저장하고 있다.
int a = 3;
int b = 0b00000000000000000000000000000011;
int c = 0b0011;
비트 OR 연산자와 비트 XOR 연산자도 이와 동일한 방식으로 진행된다.
비트 OR 연산의 예
System.out.println(3 | 10); //11
System.out.println(0b00000011 | 0b00001010); //11
System.out.println(0x03 | 0x0A); // 11
비트 XOR 연산의 예
System.out.println(3 ^ 10); //9
System.out.printnln(0b00000011 ^ 0b00001010); //9
System.out.println(0x03 ^ 0x03); //9
위에서 볼 수 있는 것처럼 일단 비트 단위의 2진수로 나열만 할 수 있으면 연산의 결과를 유추하는 것은 그리 어려운 일이 아니다.
마지막으로 비트 NOT(~)의 연산자를 살펴보자. 비트 NOT의 연산자를 이해하기 위해서는 양숫값과 음숫값을 읽는 방법을 알아야 한다. 값의 첫 번째 비트는 부호 비트(0 : 양수, 1: 음수)로 숫자의 부호를 결정한다. 일단 부호가 결정되면 나머지 비트로 값을 읽으면 되는데 양수는 1을 기준으로 값을 읽는다. 반면 음수는 0을 기준으로 값을 읽은 후에 1을 더한 값이 음수의 절댓값이다.
예를 들어
2진수 00...01010은 양수이므로 1을 기준으로 읽으며, 읽은 값은 +10( = 2의 3제곱 + 2의 1제곱)을 의미한다. 반면 11...11010은 음수이므로 0을 기준으로 읽은 값에 1을 더한 값인 6(=2의 제곱 + 2의 0제곱 + 1)에 음의 부호만 붙인 값(-6)을 의미한다.
이제 음수든 양수든 값을 읽을 수 있으므로 본격적으로 비트 NOT연산자를 알아보자. 비트 not 연산자는 0과 1을 반전시키는 연산으로 부호 비트까지 반전시키므로 비트 NOT 연산을 수행하면 항상 부호가 바뀌게 된다.
비트 NOT연산의 예
System.out.println(~3); //-4
System.out.println(~0b00000011); //-4
System.out.println(~0x03); //-4
System.out.println(~0); //-1
System.out.println(~0b00000000); //-1
System.out.println(~0x00); //-1
비트 NOT의 연산 과정
~00000011 <- 3 => 11111100 <- -4
~00000000 <- 0 => 11111111 -< -1
+)음수 값을 읽을 때 0을 기준으로 읽은 후 왜 1을 더할까?
만약 음의 정수를 읽을 때 0을 기준으로 읽고 1을 더하지 않으면 음수에서 양수로 넘어가는 과정에서 10진수 0을 나타내는 표현이 2개 존재하게 된다. 반면 0을 기준으로 읽은 후 1을 더하면 음수에서 1씩 증가시켜 양수가 되는 과정에 10진수 0을 표현하는 값이 1개만 존재한다.
//자바 메서드로 진법 변환
int data = 13;
System.out.println(Integer.toBinaryString(data));
System.out.println(Integer.toOctalString(data));
System.out.println(Integer.toHexString(data));
System.out.println();
System.out.println(Integer.parseInt("1101",2);
System.out.println(Integer.parseInt("15",8));
System.out.println(Integer.parseInt("0D",16)));
System.out.println();
//다양한 진법 표현
System.out.println(13);
System.out.println(0b1101);
System.out.println(015);
System.out.println(0x0D);
System.out.println();
//비트 연산자
//@AND 비트 연산자
System.out.println(3 & 10);
System.out.println(0b0011 & 0b1010);
System.out.println(0x03 & 0x0A);
System.out.println();
//@OR 비트 연산자
System.out.println(3 | 10);
System.out.println(0b0011 | 0b1010);
System.out.println(0x03 | 0x0A);
System.out.println();
//@XOR 비트 연산자
System.out.println(3 ^ 10);
System.out.println(0b0011 ^ 0b1010);
System.out.println(0x03 ^ 0x0A);
System.out.println();
//@NOT 비트 연산자
System.out.println(~3);
System.out.println(~0b0011);
System.out.println(~0x03);
결과