<CPP>/BASIC

18. 시프트 연산자(Shift Operator)

CodeGrimie 2021. 1. 20. 15:03

2를 곱하고 나누는 시프트 연산자

17. 비트연산자(Bitwise Operator) 에서 살펴본 비트 연산자가 { 더하기, 빼기 } 라면

시프트 연산자는 { 곱하기, 나누기 } 라고 이해하면 편하다.

 

시프트 연산자는 비트 연산자에 비해서 더 직관적으로 사용할 수 있다.

그저 단순히 비트의 진행 방향에 따라 자리 위치를 변경해주면 되기 때문이다.

 

비트의 진행 방향에 따라 빅 엔디언, 리틀 엔디언으로 나뉘는데

우리가 주변에서 흔히 볼 수 있는 64비트 컴퓨터의 CPU들은 리틀 엔디언을 사용하는 경우가 많다.

 

이 엔디언을 지금 바로 이해할 필요는 없지만 차이점은 이해하는 게 좋다.

네트워크를 개발할 때에는 빅 엔디언이 주를 이루기 때문에 리틀 엔디언 <-> 빅 엔디언을 구현해야하는 경우가 있다.

 

다시 시프트 연산자로 돌아와서 간단한 코드를 쳐보자.

 

▼ 아주 간단한 코드

#include <cstdio>

int main()
{
    int shiftValue = 2;
    
    // SHIFT UP
    printf("shiftValue << 1 = %d\n", shiftValue << 1);
    printf("shiftValue = %d\n", shiftValue);
    
    // SHIFT DOWN
    printf("shiftValue >> 1 = %d\n", shiftValue >> 1);
    printf("shiftValue = %d\n", shiftValue);
    
    return (0);
}

각각 2가 곱해지고 2가 나눠지는 걸 확인 할 수 있다.

비트의 관점에서 보면 왼쪽으로 한칸, 오른쪽으로 한 칸 이동한 것이다.

시프트 연산의 기능은 이게 전부고 CPP에서 가능한 모든 방법 중에서 가장 빠른 2를 곱하고 나누는 방법이다.

 

하지만 비트 연산자와 마찬가지로 이걸 어디다 써먹는지를 알아야 쓰던 말던 하지 않을까?

RGBA값을 HEX 값으로 바꿔보는 변환기를 작성해보자.

시프트 연산자 응용

RGBA -> HEX 변환

비트 연산과 마찬가지로 변수형의 크기 차이를 이용한 방법이다.

기본적인 개념은 4개의 변수를 보내야할 걸 1개의 변수만 보낼 수 있도록 하는 것이다.

 

▼ Int형의 크기가 Char 형의 크기보다 4배 크다는 걸 이용한다.

그림에서와 같이 1개의 Int 안에 4개의 Char값이 들어갈 수 있다.

시프트 연산이 비트의 자리를 옮길 수 있는 걸 이용해서 순서대로 자리를 8Bit씩 이동하면 된다.

 

▼ 시프트 연산을 이용한 RGBA -> HEX 변환 코드

#include <cstdio>

// RGBA -> HEX
unsigned int ConvertRgbaToHex(unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha)
{
    unsigned int _colorBuffer = 0;
    
    _colorBuffer |= (red << 24);
    _colorBuffer |= (green << 16);
    _colorBuffer |= (blue << 8);
    _colorBuffer |= alpha;

    return (_colorBuffer);
};

// PRINT HEX
void PrintHex(unsigned int color)
{
    unsigned char _red = 0, _green = 0,_blue = 0, _alpha = 0;

    _red = ((color >> 24) & 0xff);
    _green = ((color >> 16) & 0xff);
    _blue = ((color >> 8) & 0xff);
    _alpha = (color & 0xff);

    printf("COLOR(%d, %d, %d, %d)\n", _red, _green, _blue, _alpha);
}

int main()
{
    unsigned int color = ConvertRgbaToHex(100,125,150,255);
    PrintHex(color);

    return (0);
}

ConvertRgbaToHex() 함수 내부에서 각각의 RGBA값을 시프트 연산(<<)을 통해서 자리를 옮겨가며 넣고 있다.

이론적으로도 실제 코드로도 어렵지 않다.

 

반면 PrintHex() 에서 Hex 값을 다시 RGBA값으로 바꾸는 과정이 조금 복잡해 보인다.

각각의 RGBA값을 얻기 위해선 우선 시프트 연산(>>)을 통해서 다시 원래 위치 자리로 옮긴다.

Red의 경우 24만큼 위치를 왼쪽으로 이동했기 때문에 다시 24만큼 오른쪽으로 이동한다.

 

그냥 이대로 출력해도 문제는 없다.

_red = ((color >> 24));
_green = ((color >> 16));
_blue = ((color >> 8));
_alpha = (color);

하지만 프로그래밍의 관점에서 봤을 때는 혹시 모를 일을 대비하는 게 좋다.

아예 시프트 연산이 완료된 값을 1Bit(255) 범위 안에 고정시켜버리는 것이다.

 

▼ 0xff는 16진수로 1Bit(255)를 뜻한다.

_red = ((color >> 24) & 0xff);
_green = ((color >> 16) & 0xff);
_blue = ((color >> 8) & 0xff);
_alpha = (color & 0xff);

이렇게 0xff(1Bit)&(AND) 연산을 해줌으로써 Char 형의 범위 밖의 비트들은 0으로 초기화가 된다.