본문 바로가기

Program Languages/Python

파이썬 비트 연산자 사용법

현재의 보급된 컴퓨터 시스템은 0과1로 이루어진 2진수를 기본적으로 채택하고 있습니다.

 

즉, 현재의 컴퓨터 소프트웨어에서는 정보를 저장 및 표현할 수 있는 더 이상 쪼갤 수 없는 최소의 단위가 0과1로 이루어진 신호(On,Off) 2진수라고 할 수 있습니다.

 

이번 포스트에서는 파이썬에서 비트 연산자를 사용하는 방법을 알아보도록 하겠습니다.

 

우선 언급하기에 앞서 '사(4)칙 연산기호를 사용하면 되지 왜 비트 연산자를 사용해야 하냐' 라고 생각하시는 분들이 있을지 모르겠습니다.

 

여러 이유가 있겠지만 대략적인 이유는 다음과 같습니다.

  1. 연산 처리 성능을 개선하기 위해서
  2. 메모리 공간을 절약하기 위해서
  3. 기타 여러 사유

 

 

파이썬에서 사용할 수 있는 기본 비트 연산자는 OR,XOR,AND,Invert(=not연산자 적용전의 max bits value와의 diff값을 반환) 4개의 연산자와 (Left,Right)SHIFT 연산자입니다.

 

우선 OR,XOR,AND,NOT 연산에 대한 간략한 언급을 하자면,

 

위의 표와 보시는 것과 같이

  • OR은 둘 중 하나가 참이면 참값을
  • XOR은 두 값이 다르면 참값을
  • AND는 두 값이 같으면 참값을
  • NOT 게이트는 입력값의 반대값을(0이면 1, 1이면 0)
  • 그 외에는 0을 반환

의 형식으로 작동하게 됩니다.

 

이러한 연산들을 각각의 비트 자리마다 적용이 됩니다.

 

SHIFT연산을 언급하기에 앞서 컴퓨터의 데이터의 단위는 1비트,1바이트,1킬로바이트,1메가바이트, ... 등 의 단위가 있습니다.

1바이트는 8개의 비트로 구성되어져 있고 그 이후의 단위들은 SI단위계의 1000배수와 최대한 가깝지만 2진수 체계로  2의 10제곱인 1024배수로 각 단위의 차이로 두고 있습니다

 

컴퓨터 비트연산에서 SHIFT연산이란 각 비트들의 자리를 오른쪽이나 왼쪽으로 이동시키는 연산입니다.

아래는 SHIFT연산에 대한 한 예제입니다.

 

SHIFT연산에서 오른쪽이나 왼쪽으로 이동할 때 이동되고 난 후의 여백은 보급된 대부분의 컴퓨터에서 기본적으로 0으로 채워지도록 되어 있습니다.

 

따라서 shift연산을 사용할 때에 shift연산을 적용하기 전과 후의 상태가 다를 수 있으니 유의해야 합니다.

 

 

다시 본론으로 돌아와 비트 연산을 파이썬에서 사용하는 문법은 아래와 같습니다.

#파이썬에서 비트 연산자들은 정수 형태의 데이터만 가능
# | : 'OR' 게이트 비트 연산자
#       a|b = a와 b를 'or'게이트를 각 비트들에 대해 비트연산을 하겠다 라는 의미.
# ^ : 'XOR' 게이트 비트 연산자
#       a^b = a와 b를 'xor'게이트를 각 비트들에 대해 비트연산을 하겠다 라는 의미.
# & : 'AND' 게이트 비트 연산자
#       a&b = a와 b를 'and'게이트를 각 비트들에 대해 비트연산을 하겠다 라는 의미.
# ~ : 'INVERT' 게이트 비트 연산자('not'gate를 사용하기 전의 뒤집어진 비트 오프셋(1로 채워진 비트들에서부터 not연산값까지의 차이값)을 반환하는 연산)
#       ~a = a를 'invert'게이트를 각 비트들에 대해 비트 연산을 하겠다 라는 의미.
# << : 'LEFT SHIFT' 게이트 비트 연산자
#       a << 3 = a에 할당된 비트들을 왼쪽으로 3만큼 이동 시키겠다는 의미
# >> : 'RIGHT SHIFT' 게이트 비트 연산자
#       a >> 3 = a에 할당된 비트들을 오른쪽으로 3만큼 이동 시키겠다는 의미
a=2
b=3
print("a:",a,bin(a))
print("b:",b,bin(b))

v = a|b #'or'게이트 비트 연산자
print("a (bit op)or b:", bin(v))

v = a^b#'xor'게이트 비트 연산자
print("a (bit op)xor b:", bin(v))

v = a&b#'and'게이트 비트 연산자
print("a (bit op)and b:", bin(v))

a = ~a#'invert' 게이트 비트 연산자 
b = ~b#'invert' 게이트 비트 연산자



print("('invert' gate op)a:",a,bin(a))
print("('invert' gate op)b:",b,bin(b))

print("(8bit 'invert'(=not) gate op)a:",((1<<8))+a,bin(((1<<8))+a))
print("(8bit 'invert'(=not) gate op)b:",((1<<8))+b,bin(((1<<8))+b))

a = ~a#'invert' 게이트 비트 연산자 
b = ~b#'invert' 게이트 비트 연산자

print("('invert' gate op)a:",a,bin(a))
print("('invert' gate op)b:",b,bin(b))

print()
a=0b11010111

print("a:",a,bin(a))
a<<=3#왼쪽으로 3번 비트들을 이동
print("(3 rep left shift)a:",a,bin(a))

a>>=3#오른쪽으로 3번 비트들을 이동
print("(3 rep right shift)a:",a,bin(a))

a>>=3#오른쪽으로 3번 비트들을 이동
print("(3 rep right shift)a:",a,bin(a))

a<<=3#왼쪽으로 3번 비트들을 이동
print("(3 rep left shift)a:",a,bin(a))

결과가 잘 나오는 것을 알 수 있었습니다.

그런데 파이썬 결과에서 조금 이상한 것은  left shift연산입니다.

 

아까 위쪽에서 언급한 shift는 분명 left 연산을 하면 앞쪽의 비트들이 소실되어졌는데 파이썬에서는 앞쪽 비트들이 소실되지 않고 계속 값이 추가되 증가하는 것을 알 수 있습니다.

 

이는 파이썬 특유의 숫자 확장성(big integer과 유사)때문이라고도 할 수 있습니다.

즉, 파이썬의 integer는 시스템이 허용하는 한 할당되는 비트들의 크기가 확장될 수 있는 가변성을 가지고 있어 크기가 커지면 해당되는 비트수도 증가하게 됩니다.

 

이러한 특성이 좋을 때도 있지만 특정 알고리즘이나 프로젝트 솔루션에서는 간혹 좋지 않을 때도 있습니다.

 

def unsignedNotOperation(x:int,max_num_bits:int = None,use_first_method:bool = True)->int:
    
    assert type(x) is int, "Unsupported Type"
    assert (max_num_bits is None or (type(max_num_bits) is int and max_num_bits>0)), "Unsupported Type"
    if(max_num_bits is None):
        import math
        max_num_bits = int(math.floor( math.log2(x)))+1
    if(use_first_method==True):
        return (1<<max_num_bits)+(~x)
    else:
        return x^((1<<max_num_bits)-1)
def slicedLeftShift(x:int,left_shift_count:int,max_num_bits:int = None,use_first_method:bool = True)->int:
    
    assert type(x) is int, "Unsupported Type"
    assert type(left_shift_count) is int and left_shift_count>=0,"Unsupported Range"
    assert (max_num_bits is None or (type(max_num_bits) is int and max_num_bits>0)), "Unsupported Type"
    assert type(use_first_method) is bool, "Unsupported Type"
    if(max_num_bits is None):
        import math
        max_num_bits = int(math.floor( math.log2(x)))+1
    return (x<<left_shift_count)&((1<<max_num_bits)-1)

def getValueFromBooleanArray(x:int,index:int,max_num_bits:int=None)->bool:
    
    assert type(x) is int, "Unsupported Type"
    assert (max_num_bits is None or (type(max_num_bits) is int and max_num_bits>0)), "Unsupported Type"
    if(max_num_bits is None):
        import math
        max_num_bits = int(math.floor( math.log2(x)))+1
    assert 0<=index<max_num_bits,"Unsupported Range"
    index = 1<<index
    return (x&index)==index
def putValueToBooleanArray(x:int,index:int,value:bool,max_num_bits:int=None)->int:
    
    assert type(x) is int, "Unsupported Type"
    assert (max_num_bits is None or (type(max_num_bits) is int and max_num_bits>0)), "Unsupported Type"
    if(max_num_bits is None):
        import math
        max_num_bits = int(math.floor( math.log2(x)))+1
    assert 0<=index<max_num_bits,"Unsupported Range"
    index = 1<<index
    if(value==True):
        x = (x|index)
    elif((x&index)==index):
        x = (x^index)
    return x
def main():
    a=0b11010111

    print("a:",a,bin(a))
    #a<<=3#왼쪽으로 3번 비트들을 이동
    a =slicedLeftShift(a,3,max_num_bits=8)
    print("(3 rep left shift)a:",a,bin(a))

    a>>=3#오른쪽으로 3번 비트들을 이동
    print("(3 rep right shift)a:",a,bin(a))

    a>>=3#오른쪽으로 3번 비트들을 이동
    print("(3 rep right shift)a:",a,bin(a))

    a<<=3#왼쪽으로 3번 비트들을 이동
    print("(3 rep left shift)a:",a,bin(a))

    
    print()
    x = 0b100

    x = putValueToBooleanArray(x,4,True,max_num_bits=8)
    print("x:",bin(x))

    x = putValueToBooleanArray(x,5,True,max_num_bits=8)
    print()
    print("x:",bin(x))
    print("\t","x[1]:",getValueFromBooleanArray(x,1,max_num_bits=8))
    print("\t","x[2]:",getValueFromBooleanArray(x,2,max_num_bits=8))
    print("\t","x[3]:",getValueFromBooleanArray(x,3,max_num_bits=8))

    print()
    x = putValueToBooleanArray(x,2,False,max_num_bits=8)
    print("x:",bin(x))
    print("\t","x[2]:",getValueFromBooleanArray(x,2,max_num_bits=8))


    a=2
    b=3
    a = unsignedNotOperation(a)
    b = unsignedNotOperation(b)

    print("('not' gate op)a:",a,bin(a))
    print("('not' gate op)b:",b,bin(b))

    a=2
    b=3

    a = unsignedNotOperation(a,8)
    b = unsignedNotOperation(b,8)

    print("('not' gate op)a:",a,bin(a))
    print("('not' gate op)b:",b,bin(b))
if(__name__=="__main__"):
    main()

코드를 조금 추가해주니 위쪽에서 언급된 형식처럼 나오는 것을 알 수 있었습니다.