티스토리 뷰

Table of Contents


개요

비트연산자 모음

<<, >> (shift 연산)

& (and 연산)

| (or 연산)

^ (xor 연산)

~ (not 연산)

이진수의 음수 표현 : 보수




1. 개요


 C언어에는 변수를 비트 단위로 조작할 수 있는 연산자들이 존재합니다. 이들을 비트연산자라고 하며, 비트연산자로는 <<, >>, &, |, ^, ~ 등이 있습니다. 여기에서는 비트연산자들의 연산 과정과 함께 이진수의 음수 표현에 대해 소개합니다. 이진수에 대해 모르거나 잘 기억이 안 난다면 이진수와 비트단위 를 먼저 보고 오시는 걸 추천합니다.




2. 비트연산자 모음


 각 비트연산자가 일으키는 연산을 간단하게 설명해놓은 표입니다. (아래의 예시에 쓰인 숫자들은 십진수가 아니라 이진수입니다.)

연산자

사용 형태

예시

예시 결과

설명

<< (shift)

a<<b

11011 << 2

1101100

a의 비트들을 b 만큼 왼쪽으로 이동 시킨다.

>> (shift)

a>>b

11011 >> 2

110

a의 비트들을 b 만큼 오른쪽으로 이동 시킨다.

& (and)

a&b

1001 & 1010

1000

두 수에서 모두 1인 비트 단위에만 1이 들어간다.

| (or)

a|b

1001 & 1010

1011

한 수 이상에서 1인 비트 단위에만 1이 들어간다.

^ (xor)

a^b

1001 & 1010

11

두 수가 서로 다른 비트 단위에만 1이 들어간다.

~ (not)

~a

~00100100

11011011

원래 수에서 0인 비트단위에는 1, 1인 비트 단위엔 0이 들어간다.




3. <<, >> (shift 연산)


1. <<


 a<<b 는 'a의 비트들을 b 만큼 왼쪽으로 이동 시킨다.'는 뜻입니다. 이 때, 빈 곳이 되는 오른쪽의 비트 단위들에는 0이 채워집니다. 아래의 프로그램을 돌려보면 어떤 식으로 이루어지는 지 감을 익힐 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
//code by RiKang, weeklyps.com
#include <stdio.h>
int main(void) {
    int a = 3// 3 = 11
    printf("3<<1 = %d\n",a<<1); //  110 = 6
    printf("3<<2 = %d\n",a<<2); //  1100 = 12
    printf("3<<3 = %d\n",a<<3); // 11000 = 24
    
    a = 5// 5 = 101
    printf("5<<1 = %d\n",a<<1); //  1010 = 10
    printf("5<<2 = %d\n",a<<2); //  10100 = 20
    printf("5<<3 = %d\n",a<<3); // 101000 = 40
    return 0;
}
cs


 이진수의 표현에서 대개 왼쪽 비트 단위는 오른쪽 비트단위의 2배이지요. 따라서 a의 비트들을 모두 왼쪽으로 하나씩 이동시키면 a가 2배가 되는 경우가 많습니다. 또한 이를 이용해 a * 2 를, a << 1로 대체하는 경우가 존재합니다. 같은 원리로 를 a<<b로 대체하기도 합니다.


 a = a<<b; 는 a<<=b; 로 축약 가능합니다.


2. >>


 a>>b 는 'a의 비트들을 b 만큼 오른쪽으로 이동 시킨다.'는 뜻입니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//code by RiKang, weeklyps.com
#include <stdio.h>
int main(void) {
    int a = 13// 13 = 1101
    printf("13>>1 = %d\n",a>>1); // 110 = 6
    printf("13>>2 = %d\n",a>>2); //  11 = 3
    printf("13>>3 = %d\n",a>>3); //  1 = 1
    printf("13>>4 = %d\n",a>>4); //  0 = 0
    
    a = 21// 21 = 10101
    printf("21>>1 = %d\n",a>>1); // 1010 = 10
    printf("21>>2 = %d\n",a>>2); //  101 = 5
    printf("21>>3 = %d\n",a>>3); //  10 = 2
    printf("21>>4 = %d\n",a>>4); //  1 = 1
    printf("21>>5 = %d\n",a>>5); //  0 = 0
    return 0;
}
cs



 << 와는 반대로, a의 비트들을 모두 오른쪽으로 하나씩 이동시키면 a가 2로 나눠지는 변수들이 많이 있습니다. (나눗셈 연산에서도 정수형 변수 / 정수형 변수 일 경우. 나머지는 버려집니다.) 이를 이용해 a / 2 를, a >> 1로 대체하는 경우가 있으며,  같은 원리로 를 a>>b로 대체하기도 합니다.


 a = a>>b; 는 a>>=b; 로 축약 가능합니다.




4. & (and 연산)


 &를 하면 양쪽의 두 수에서 모두 1인 비트 단위에만 1이 들어갑니다.  아래는 1비트 숫자들의 & 연산 결과입니다.


예시

결과 

1 & 1 

1

1 & 0

0

0 & 1

0

0 & 0

0


 여러 비트를 가진 숫자들의 연산은 각각의 비트 단위들에 대해 독립적으로 이루어집니다.


1
2
3
4
5
6
7
8
9
//code by RiKang, weeklyps.com
#include <stdio.h>
int main(void) {
    printf("12&9 = %d\n"12&9); // 1100 & 1001 = 1000 = 8
    printf("12&3 = %d\n"12&3); // 1100 & 0011 = 0000 = 0
    printf("12&5 = %d\n"12&5); // 1100 & 0101 = 0100 = 4
    printf("12&7 = %d\n"12&7); // 1100 & 0111 = 0100 = 4
    return 0;
}
cs


&에 대해 알아두면 좋은 문제


 두 자연수 a, b가 입력으로 주어질 때, a 의 b 번째 비트가 1이면 yes, 0이면 no를 출력하시오.

(* a 의 1의 단위를 0 번째 비트, 2의 단위를 1 번째 비트, 4의 단위를 2 번째 비트,... 라고 하자.)

(* shift 연산을 함께 사용하면 편하다.)





5. | (or 연산)


 |를 하면 한 수 이상에서 1인 비트 단위에만 1이 들어갑니다. 아래는 1비트 숫자들의 | 연산 결과입니다.


예시

결과

1 | 1

1

1 | 0

1

0 | 1

1

0 | 0

0


 위의 연산이 각각의 비트 단위들에 대해 이루어집니다.


1
2
3
4
5
6
7
8
9
//code by RiKang, weeklyps.com
#include <stdio.h>
int main(void) {
    printf("12|9 = %d\n"12|9); // 1100 | 1001 = 1101 = 13
    printf("12|3 = %d\n"12|3); // 1100 | 0011 = 1111 = 15
    printf("12|5 = %d\n"12|5); // 1100 | 0101 = 1101 = 13
    printf("12|7 = %d\n"12|7); // 1100 | 0111 = 1111 = 15
    return 0;
}
cs




6. ^ (xor 연산)


 ^를 하면 두 수가 서로 다른 비트 단위에만 1이 들어갑니다. 아래는 1비트 숫자들의 |^연산 결과입니다.


예시

결과

1 ^ 1

0

1 ^ 0

1

0 ^ 1

1

0 ^ 0

0


 위의 연산이 각각의 비트 단위들에 대해 이루어집니다.


1
2
3
4
5
6
7
8
9
//code by RiKang, weeklyps.com
#include <stdio.h>
int main(void) {
    printf("12^9 = %d\n"12^9); // 1100 ^ 1001 = 0101 = 5
    printf("12^3 = %d\n"12^3); // 1100 ^ 0011 = 1111 = 15
    printf("12^5 = %d\n"12^5); // 1100 ^ 0101 = 1001 = 9
    printf("12^7 = %d\n"12^7); // 1100 ^ 0111 = 1011 = 11
    return 0;
}
cs




7. ~ (not 연산)


 ~를 하면 0은 1로, 1은 0으로 바뀌게 됩니다. 아래는 1비트 숫자들의 ~연산 결과입니다.


예시

결과

~ 0

1

~ 1

0


위의 연산이 각각의 비트 단위들에 대해 이루어집니다.


1
2
3
4
5
6
7
8
9
//code by RiKang, weeklyps.com
#include <stdio.h>
int main(void) {
    int a = 12;                     // 0000 0000 ....0000 1100
    printf("(int) ~12 = %d\n", ~a); // 1111 1111 ....1111 0011
    unsigned int b = 12;                     // 0000 0000 ....0000 1100
    printf("(unsigned int) ~12 = %u\n", ~b); // 1111 1111 ....1111 0011
    return 0;
}
cs


 출력값


(int) ~12 = -13 (unsigned int) ~12 = 4294967283

 위의 출력값을 살펴보도록 하겠습니다. 우선 int 와 unsigned int 는 4 바이트, 즉, 32 비트를 사용하며 12를 넣으면 8보다 큰 단위의 비트들은 모두 0으로 되어있습니다.  ~을 쓰면 그 큰 단위의 0들이 모두 1로 바뀌니, ~b 가 큰 수로 출력되는 것은 당연한 이치입니다.


 그런데 ~a 는 큰 수는 커녕 -13 으로 출력되었습니다. 이를 이해하기 위해선 이진수의 음수 표현에 쓰이는 보수의 개념에 대해 알아야 합니다.




8. 이진수의 음수 표현 : 보수


 C언어에서는 부호가 있는 변수에서 양수를 표현할 때, 가장 큰 비트는 0으로 고정시킵니다. 그렇게 함으로서 가장 큰 비트가 0 이면 양수, 1 이면 음수로 간단하게 판별할 수 있는 것이지요. 인간이 이해하도록 쉽게 이 방식을 적용하면 아래와 같이 될 것입니다.


0000 0000 .... 0000 1100  =  12

1000 0000 .... 0000 1100  =  -12


 하지만 이런 방식은, 컴퓨터가 사칙연산을 수행할 때 비효율적이기 때문에 잘 쓰이지 않습니다. 게다가 0을 표현하는 이진수가 2개가 된다는 단점도 있지요. ( 0000 0000 = 0 = -0 = 1000 000 )


 그 대신에 C언어에서는 를 로 인식하는 방법을 취합니다. (32bit 부호가 있는 정수, int 의 예시입니다.) 이렇게 하면 가장 큰 비트가 0 이면 양수, 1 이면 음수로 판별할 수 있는 건 같지만, 다른 비트 단위에서의 값이 달라지게 됩니다. 아래는 12에 대한 예시입니다.

(가 감이 안잡힌다면 대략(64 - a) 정도로 계산해보는 것을 추천합니다. 어차피 큰 비트들은 다 1이 되니까요.)


0000 0000 .... 0000 1100  =  12

1111 1111 .... 1111 0100  =  -12

(2^32 - 12 를 -12로 인식합니다.)


 이러한 방식을 2의 보수라고 합니다. 이를 통해 왜 ~12를 출력하였을 때 -13이 나왔는지 알아보겠습니다.


12 = 0000 0000 .... 0000 1100

~12  = 1111 1111 .... 1111 0011 (not 연산으로 0->1, 1->0 으로 변환되었습니다.)


13 = 0000 0000 .... 0000 1101

-13 = 1111 1111 .... 1111 0011 (2^32 - 13으로 계산한 결과입니다.)


 위의 결과에서 알 수 있듯이, int로 저장할 때 ~12 와 -13은 같은 수가 됩니다. 이러한 특징을 이용해 2의 보수를 계산할 땐, 0을 1로 1을 0으로 모두 바꾼 후, 1을 더해주면 편하게 계산할 수 있습니다.


-12  = 

~12 +1 = 

~(0000 0000 .... 0000 1100) + 1 = 

1111 1111 .... 1111 0011 + 1 = 

1111 1111 .... 1111 0100