본문 바로가기

별걸다하는 IT/프로그래밍언어

[프로그래밍C++,Java 생기초] 비트 연산자1(bitwise operator)- & AND연산자 개념 정리 및 활용, 실습 예제(bitset, toBinaryString)

[Java, C, C++ 프로그래밍 강좌 목차 편, 링크 모음]

 

오늘 프로그래밍 기초 편에서는 연산자 중 비트 연산자에 대해 다뤄볼게요

처음에 프로그래밍을 배울 때 대입연산자나 산술연산자 같은 경우는 빠르게 배워요 ㅎㅎ 수학과 유사하거든요

그런데 비트연산자인 경우 아무래도 컴퓨터 수체계에서 다뤄지는 형태다보니까 익숙해하지 않더라고요

하지만 어떤 알고리즘 로직을 짤 때도 정말 유용하게 다뤄질 경우가 많고 ( 연산 수를 줄이거나 메모리 공간 효율성을 높여주는 용도로 ㅎㅎ) 지금 익숙하지 않을지라도 하드웨어 관련 프로그래밍에 잘 활용되므로 꼭 짚고 넘어가기!

 

비트 연산자의 종류

시작하기 전에 비트 연산자가 뭐뭐 있는지 표로 정리하고 갈게요

연산자

연산자의 기능

결합방향

&

비트 단위로 AND 연산을 한다. ex) num1 & num2

->

|

비트 단위로 OR 연산을 한다. ex) num1 | num2

->

^

비트 단위로 XOR 연산을 한다. ex) num1 & num2;

->

~

단한 연상자로서 피연산자의 모든 비트를 반전시킨다.

ex) ~num;

<-

<<

피연산자의 비트 열을 왼쪽으로 이동시킨다.

ex) num<<2; //num은 변화 없음, 두 칸 왼쪽 이동 결과 반환

->

>>

피연산자의 비트 열을 오른쪽으로 이동시킨다.

ex) num>>2; //num은 변화 없음, 두 칸 오른쪽으로 이동 결과만 반환

->

참고로 <<,>>는 비트를 연산하기보다 이동시키기 때문에 비트 이동 연산자(쉬프트 연산자)에 좀 더 가깝지만 결국 다 비트를 다루기 때문에 한 데 묶었어요.

 

중요한건 단위가 비트(BIT)라는거~!!~~ 이진수체계에서 노는, 컴퓨터 수체계에서 노는~ 연산자예요

 

& - AND

먼저 맨 앞에 있는 &(AND) 비트부터 살펴볼께요. 덧셈이 수학에서 두 수를 더해 그 값을 반환하는 작용을 해주는 것처럼 & 또한 두 비트에 어떤 연산을 투닥투닥 한 뒤 결과를 반환해줍니다. 즉 산술연산이나 비트연산이나 똑같은 연산자!

&는 두 비트가 모두 1일 때 1을 반환하고 하나라도 0이면 0을 반환합니다.

 

실습 1

실습을 통해서 바로 확인 고고 

--c언어
#include <stdio.h>
int main()
{
   int a = 10, b = 2;
   printf("a & b = %d ", a & b);
   return 0;
}

자바

결과가 왜 이와 같이 나왔는지 생각을 해볼까요?

출처 : https://www.2braces.com/c-programming/c-bitwise-operators

둘 다 1인 부문만 1이 되고 0이 하나라도 겹치면 0이 됐어요 ㅎㅎ

2진수로 0...0010을 10진수로 변경하면 2가 되니까 2가 출력된겁니다.

 

실습2 - 가시적으로 이진수를 확인하면서 하는 실습

[&실습- C++언어]

밑에 가면 자바도 있으용~

#include <iostream>
#include <bitset>
using namespace std;

int main()
{
	int num1 = 15, num2 = 20, result;
	cout << "10진수 num1:"<< num1 
		<< ",\t 2진수 num1 :"<<bitset<8>(num1) << endl;
	cout << "10진수 num2:" << num2
		<< ",\t 2진수 num2 :" << bitset<8>(num2) << endl;
	result = num1 & num2;
	cout << "10진수 결과:" << result 
		<< ",\t 2진수 결과 :"<< bitset<8>(result)<<endl;
	return 0;
}

이렇게 작성해주세요 ㅎㅎ '\t'는 탭으로 일정한 간격을 띄어준다는 뜻이고 

bitset은 정수를 이진수로 쉽게 표현하기 위해서 사용한 라이브러리예요

bitset<8>(10); --> 10이라는 숫자를 이진수 8자리로 보여줘!

bitset<8>에서 <>사이 숫자는 몇 비트로 숫자를 나타낼 건지를 의미합니다. ()사이는 내가 이진수로 보고 싶은 정수를 넣어주면 됩니다!

 

무튼 위 코드를 실행시켜보면 결과는!!

요렇게 해당 숫자를 8개의 자리수로 변환해서 보여줍니다. ㅎㅎ

AND(&) 연산을 수행해줬더니 두 비트가 모두 1일 때만 결과의 그 자리수 비트를 1로 반환하고

나머지 0이 하나라도 있을 경우 모조리 0처리 했네요~

 

2진수 문자열로 표현하기 -Java언어

자바는 차근차근 과정을 보여주면서 진행할게요. 조금 C++보다는 복잡해서?

자바의 경우 비트 연산 결과를 2진수로 출력하기 위해 toBinaryString()이라는 메서드를 사용할 수 있습니다.

이 메서드는 4byte의 정수를 32자리의 2진수로 변환해요 ㅎㅎ

public class Main {
	 public static void main(String[] args) {
		 int num1 = 15, num2 =20;
         //이진수를 문자열로 출력함으로써 내가 확인하기 위함!
		 String bit1 = Integer.toBinaryString(num1); //num1을 이진수문자열로 변경
		 String bit2 = Integer.toBinaryString(num2); //num2를 이진수 문자열로 변경
		 System.out.println(bit1);
		 System.out.println(bit2);
	 }
}

결과창

근데 결과 값을 확인해보면 앞에 남는 0을 0이라고 명시해서 보여주는게 아니라 생략해버려서 비교하기가 초큼 불편해요.

자리수를 끝에서부터 맞춰야 비교하기가 편한데 지금 경우, 결과가 맞는지 한번에 확인하게 쉽지않죠ㅎㅎ

그래서 앞에 0을 붙여주기 위한 방법이 대표적으로 두 가지가 있습니다.

 

1. "00000000"을 앞에 넉넉히 더해준다음 원하는 자리수만큼 잘라내기

2. 자리수를 맞춰준 후, replace를 이용해서 ' '를 '0'으로 직접 치환해주는 방법!

 

1번 방법

1번 방법을 통해서 bit를 0과 함께 출력해볼게요. toBitString은 최대 32자리니까 앞에 "0"을 32개 더 붙인 다음, 원하는 길이만큼 잘라냅시다.

public class Main {
	 public static void main(String[] args) {
		 int num1 = 15, num2 =20;
		 String bit1 = toBinaryString(num1);
		 String bit2 = toBinaryString(num2);
		 System.out.println(bit1);
		 System.out.println(bit2);
	 }
	 static String toBinaryString(int x) { //새로 정의
		 String zero = "00000000000000000000000000000000";
		 String tmp = zero+Integer.toBinaryString(x);
		 return tmp.substring(tmp.length()-8);
	 }
}

Integer.toBinaryString이 "0"을 안나타내는게 싫어서

Integer.toBinaryString을 이용해서 toBinaryString함수를 새로 만든 후, 그걸 사용해줬어요.

(Integer.toBinaryString은 라이브러리가 제공해주는 기능이고, toBinaryString은 우리가 만든 함수예요)

우리가 정의한 함수 toBinaryString(int x) 내부를 보면 32개의 '0'을 갖는 zero 문자열을 앞에 더해줬죠? 

그럼 num1의 경우 총 36길이가 됐을거예요(num1의 경우 1111으로 4의길이를 가지므로!)

문자열을 자르는 subString함수를 이용해서 36-8인 앞의 28자리를 잘라주고 뒤의 8자리만 사용했어요

 

2번 방법 - 공백을 0으로 치환

두 번째 방법을 사용해볼까요.

먼저 String.format을 써서 자리수를 맞춰줬을 때 결과가 어떻게 나오는지 확인해봅시다.

public class Main {
	 public static void main(String[] args) {
		 int num1 = 15, num2 =20;
		 String bit1 = String.format("%8s", Integer.toBinaryString(num1));
		 String bit2 = String.format("%8s", Integer.toBinaryString(num2));
		 System.out.println(bit1);
		 System.out.println(bit2);
	 }
}

8자리 중 앞에는 공백으로 채워지고 수가 들어간 것을 알 수 있어요. 이제 여기다가 replace를 이용해서 ' '을 '0'으로 바꿔봅시다.

public class Main {
	 public static void main(String[] args) {
		 int num1 = 15, num2 =20;
		 String bit1 = String.format("%8s", Integer.toBinaryString(num1)).replace(' ', '0');
		 String bit2 = String.format("%8s", Integer.toBinaryString(num2)).replace(' ', '0');
		 System.out.println(bit1);
		 System.out.println(bit2);
	 }
}

 

짠 결과가 원하는 형태로 만들어졌어요! ㅎㅎ 이제 자바로 이진수를 표현하는 법을 알았으니 &실습을 진행해볼까요

[&실습- JAVA언어]

public class Main {
	 public static void main(String[] args) {
		 int num1 = 15, num2 =20;
		 String bit1 = String.format("%8s", Integer.toBinaryString(num1)).replace(' ', '0');
		 String bit2 = String.format("%8s", Integer.toBinaryString(num2)).replace(' ', '0');
		 System.out.println(bit1);
		 System.out.println(bit2);
		 System.out.println(String.format("%8s", Integer.toBinaryString(num1 & num2)));
	 }
}

결과 값을 출력할 때는 replace를 생략해줘도 됩니다. 기존의 num1은 ' '이 있어서 제거해줬지만 결과값에 영향을 미치는 num1과 num2의 공백을 사전에 제거해준 뒤 연산을 수행했기 때문이죠 ㅎㅎ

AND 연산이 잘 수행된 것을 확인하실 수 있습니다.

 

활용 예시

이러한 특징 때문에 &연산자는 짝수 홀수를 판별하는 데에도 쓰일 수 있습니다.

홀수는 2로 나누어지지 않기 때문에 2진수의 마지막 자리가 꼭 1이여야하거든요!! 1은 1을 만났을 때만 1이 되죠? 다른걸 만나면 다 0으로 만들어버려요. 즉 '1&어떤 수'를 했을 때 이 값이 1이면 얘는 홀수다!! 

[C++]

#include <iostream>
#include <bitset>
#include <math.h>
#include <time.h>
using namespace std;

int main()
{
	int randomNumber;
	srand(time(NULL));
	randomNumber = rand() % 100;
	if (randomNumber & 1) {
		cout << randomNumber<< "는 홀수야!" << endl;
	}
	else {
		cout << randomNumber << "는 짝수야!" << endl;
	}
	return 0;
}

생기초 프로그래밍 이니만큼 좀 세세하게 설명을 하자면,

랜덤한 수를 구하는 코드가 두 줄 들어갔어요. rand()%100은 math.h라이브러리에 있는 함수로 0~100까지 랜덤한 수를 반환해줍니다. rand()%10하면 0~10까지 수 중 한 숫자가 뽑히겠죠?

srand(time(NULL))의 경우 지금은 일단, 계속 변하는 시간적 특징을 이용해서 수가 계속 랜덤하게 변하도록 도와줬다 생각하면 돼요ㅎㅎ

 

나머지 아래 코드는 설명한대로, randomNumber &1 이 1일 경우 (1은 true를 의미하기도 함) 홀수라는 걸!

결과가 0일 경우 짝수라는걸 확인하기 위한 코드입니다.

실행 결과 저는 44가 랜덤수로 떴네요 ㅎㅎ 잘 작동하는 것을 확인할 수 있습니다. 이렇게 &를 이용해서 짝수홀수를 판별하는건 모듈연산(%- 나머지 수를 이용)을 이용하는 것보다 대략 66% 정도 성능 향상을 보여주네요 ㅎㅎ.

 

[짝수 홀수 판별 - Java 버전입니다]

public class Main {
	 public static void main(String[] args) {
		 Random random = new Random();
		 int randomNumber = random.nextInt(100);
		 if((randomNumber & 1) >0 ) {
			 System.out.println(randomNumber +"은 홀수입니다.");
		 }else {
			 System.out.println(randomNumber +"은 짝수입니다.");
		 }
	 }
}

로직은 똑같아요 ㅎㅎ 차이가 있다면 random함수 만드는 방식?ㅎㅎ자바에는 java.util.Random을 임포트해줍시다.ㅎㅎ

 

[C++ 특정 flag 확인]

이 외에 몇 번 스위치가 켜져있는지, 즉 어떤 특정 플래그가 켜져있는지 검사하는 데 사용할 수도 있어요.

switch는 조건문에서 배우는 switch 문법과 이름이 겹치므로 flag로 변수명을 설정해줄게요.

앞에서부터 1번 스위치 2번 스위치를 뜻한다고 생각해봅시다.

#include <iostream>
#include <bitset>
using namespace std;

int main()
{
	char flag = 5;
	cout << bitset<4>(flag) << endl; //0101로 두 번째 스위치와 마지막 스위치가 켜져있네요
	if (flag & 1) {
		cout << "마지막 스위치가 켜져 있다" << endl;
	}
	if (flag & 2) {
		cout << "세 번째 스위치가 켜져 있다" << endl;
	}
	if (flag & 4) {
		cout << "두 번째 스위치가 켜져 있다" << endl;
	}
	if (flag & 8) {
		cout << "첫 번째 스위치가 켜져 있다" << endl;
	}
	return 0;
}

결과창은 아래와 같습니다.

하나의 연산자만 다뤘을 뿐인데..

이진수를 표현하는 법까지 포함해서 설명하려다보니 글이 길어졌군요!!!

여기서 마무리하겠습니다. 좋은 글이었다~!, 공감으로 소소하게 표현해주세요 ㅎㅎ

다음에 또 봐요!!