안녕하세요. 지난 포스팅의 넘파이 알고 쓰자 - 넘파이 원소 재배열하기에서는 영상 처리에서도 자주 사용되는 flip 함수들과 rot90 함수에 대해서 설명하였습니다. 오늘은 and, or, xor과 같은 논리 연산과 쉬프트 연산인 left_shift와 right_shift에 대해서 알아보도록 하겠습니다. 오늘 역시 아주 간단하기 때문에 쉽게 이해하실 수 있습니다.
1. numpy.bitwise_and(x1, x2)
먼저 논리 연산자 중에서 많이 쓰이는 and 연산과 관련된 함수입니다. 파이썬에서도 이 and 연산은 조건문이나 반복문의 탈출 조건을 명시할 때 사용되는 흔한 함수입니다. 하지만, 이를 비트 단위로 바꿔서 생각해보도록 하겠습니다. 여기서 비트란 정보를 표현하는 최소단위를 의미합니다. 여러분들의 컴퓨터와 저희 컴퓨터는 내부적으로 2진수(0과 1)로 모든 연산을 가능하게 합니다. 따라서, 여기서 말씀드리는 and 연산은 비트 단위의 연산을 의미합니다. 예를 들어서 생각해보도록 하겠습니다. 13은 이진수로 변환하면 1101과 동일합니다. 그리고 17을 2진수로 변환하면 10001과 동일합니다. 13과 17에 비트별로 and 연산을 취한다는 것은 이진수로 변환된 자리수에 맞춰서 두 비트가 둘 다 1이면 1, 둘 중에 하나라도 0이면 0을 반환하는 함수를 의미합니다. 하지만 13은 4자리고 17을 5자리죠? 따라서 이진수로 변환된 1101앞에 0을 추가하여 5자리로 맞춰줍니다. 어차피 0은 13이라는 숫자에 어떠한 영향도 주지 않기 때문입니다. 그러면 13은 01101, 17은 10001로 이제 자리수에 맞춰서 and 연산을 취해주면 00001이 나오게 됩니다. 마지막 자리수만 둘 다 1이기 때문에 이러한 결과가 나오는 것이죠. 따라서 최종적으로 10진수로 변환하여 1이라는 값을 반환하게 됩니다. 정말로 그러는 지 bitwise_and 함수를 이용해서 확인해보도록 하겠습니다.
np.bitwise_and(13, 17) # 1
정말로 1이 나왔습니다. 다른 예제를 보도록 하겠습니다. 이번에는 14와 13을 보도록 하죠. 14 = 1110이고 13 = 1101입니다. 이는 쉽게 계산하실 수 잇겠죠? 이번에는 비트의 수도 동일하기 때문에 바로 and 연산을 취해주면 1100이 되어 10진수로 변환하게 되면 12라는 값이 반환될 것이라고 예상할 수 있습니다.
np.bitwise_and(14, 13) # 12
만약, 배열이 들어간다면 어떻게 될까요? 크게 2가지의 경우로 나눌 수 있을 것입니다. 두 배열의 길이가 다른 경우와, 두 배열의 길이가 같은 경우입니다. 두 배열의 길이가 다른 경우에는 브로드캐스팅이 가능하다면 적은 길이를 가지는 배열이 브로드캐스팅되어 비트별 and 연산을 취해주게 됩니다. 만약, 브로드캐스팅이 안되면 당연하게 오류가 발생하겠죠.
np.bitwise_and([14,17], 13) # array([12, 1])
np.bitwise_and([14,17], [13, 12, 11
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-8-67bf73774299> in <module>
----> 1 np.bitwise_and([14,17], [13, 12, 11]) # array([12, 1])
ValueError: operands could not be broadcast together with shapes (2,) (3,)
입력되는 두 배열의 길이가 같은 경우에는 아래와 같이 동일한 인덱스에 해당하는 원소끼리 비트별 and 연산을 취해주면 됩니다.
np.bitwise_and([14,17], [13,13]) # array([12, 1])
2. numpy.bitwise_or(x1, x2)
다음은 and 연산과 마찬가지로 자주 사용되는 or 연산입니다. 이 함수는 bitwise_and 함수와 마찬가지로 입력된 두 숫자의 이진수를 얻은 뒤 비트별로 or 연산을 취해줍니다. 이때 and 연산과 다른 점은 두 비트 중 하나라도 1이라면 1을 반환하고 둘 다 0이라는 0을 반환한다는 점입니다. 간단하게 예제 코드를 보고 넘어가도록 하겠습니다. 예를 들어 방금봤던 13, 17을 경우에는 각각 01101, 10001이기 때문에 비트별 or 연산을 취해주면 11101이 나와서 10진수로 변환해주면 29라는 결과를 얻을 수 있습니다.
np.bitwise_or(13, 17) # 29
나머지 기능은 bitwise_and 함수와 동일하기 때문에 생략하도록 하겠습니다.
3. numpy.bitwise_xor(x1, x2)
논리연산자 중에서 and, or, not를 특별히 기본 논리 연산자라고 부르기도 합니다. 이 기본 논리 연산자들을 이용해서 수많은 논리연산자들을 구현할 수 있기 때문이죠. 대표적인 예시가 바로 xand 연산과 xor 연산입니다. xand 연산은 두 비트가 서로 다르면 0, 서로 같으면 1을 반환하고 xor 연산은 xand 연산과는 반대로 두 비트가 서로 다르면 1, 서로 같으면 0을 반환하는 함수입니다. 하지만, 넘파이에는 xor 연산만 구현되어 있습니다. 그 이유는 xor 연산을 이용해서 xand 연산을 쉽게 구현할 수 있기 때문이라고 생각합니다. 또는, 뜬금없지만 딥러닝에서도 xor 연산을 굉장히 중요하게 다루기 때문이 아닌가라는 생각이 들기도 하네요. 이번에도 동일하게 13, 17를 살펴보면 각각 01101, 10001이기 때문에 비트별로 xor 연산을 취해주면 11100이 되서 10진수로 변환해주면 28이라는 결과를 얻을 수 있습니다.
np.bitwise_xor(13, 17) # 28
나머지 기능은 bitwise_and 함수와 bitwise_or 함수와 동일하기 때문에 생략하도록 하겠습니다.
4. numpy.invert(x)
다음은 기본 논리 연산자인 not 연산입니다. 이는 단순히 1 -> 0, 0 -> 1으로 뒤집는 연산이기 때문에 invert 연산이라고도 불립니다. 예를 들어 17을 보면 10001에 not 연산을 취하면 그 결과는 01110이 되어 14라는 결과를 얻을 수 있을 것이라고 생각하실 수 있겠지만!! 아쉽게도 아닙니다. 답은 경우에 따라 다릅니다.
왜냐하면 먼저 내부적으로 컴퓨터는 가장 앞비트는 부호 비트로 인식합니다. 가장 앞이 0이면 양수, 1이면 음수로 인식합니다. 그리고 다음으로 봐야할 것은 이 숫자가 몇 비트 기반인지 봐야합니다. 예를 들어서 8비트 기반의 17은 00010001로 나타냅니다.
이제 이러한 사전지식을 이해한 뒤에 8비트 기반이라고 가정한 17에 not 연산을 적용하면 11101110을 얻을 수 있습니다. 마지막으로!! 알아야할 것은 컴퓨터는 음의 정수를 다룰 때는 2의 보수라는 수 체계를 활용합니다.
간단하게 예시로 설명하면 4비트의 1010이 있다고 가정하겠습니다. 이 수는 가장 앞의 숫자가 1이기 때문에 컴퓨터는 음수로 인식할 것입니다. 그렇다면 그 크기는 어떻게 계산할까요? 단순하지만 확실한 방법이 있습니다. 바로 1010에 어떤 수를 더해야 10000이 나오는 지 알면됩니다. 어차피 4비트 기반이기 때문에 5번째 자리수는 없는 것이나 마찬가지입니다. 따라서 0000이 나오는 수를 찾는 것과 같은 문제입니다. 이 경우에는 0110을 더하면 될 것입니다. 이 수는 가장 앞 비트가 0이기 때문에 +6으로 인식할 것입니다. 그런데 +6이랑 더해서 4비트 기반의 0000이 나오는 방법은 10진수 상에서 -6밖에 없습니다!! 따라서 4비트 기반 2의 보수에서는 1010은 -6으로 인식됩니다.
그렇기 때문에 11101110도 2의 보수를 이용해서 계산하면 00010010이 되면 됩니다. 이 수는 10진수 상으로 +18이기 때문에 컴퓨터 내부적으로는 11101110을 -18로 인식할 것입니다. 아래의 코드를 보시면 실제로 그러는 것을 볼 수 있습니다. 물론 비트수가 달라지거나 2의 보수 체계가 아닌 경우에는 이 규칙이 만족하지 않습니다.
np.invert(np.array(17, dtype=np.int8)) #-18
예를 들어 17의 데이터 타입이 np.uint8이라고 가정해보면 8비트 기반의 unsigned integer이기 때문에 부호가 존재하지 않습니다. 따라서 17은 00010001이고 이에 not 연산을 취하면 11101110이 됩니다. 이때 부호 비트가 존재하지 않기 때문에 평소에 2진수를 10진수로 변환하듯이 하면 됩니다. 그러면 238이라는 결과를 얻을 수 있습니다.
np.invert(np.array(17, dtype=np.uint8)) #238
5. numpy.left_shift(x1, x2), numpy.right_shift(x1, x2)
다음은 컴퓨터의 기본 연산 중에 하나인 shift 연산입니다. 이 shift 연산은 왼쪽으로 shift하는 지, 오른쪽으로 shift하는 지에 따라서 left shift 연산과 right shift 연산으로 2개의 타입이 존재합니다.
예를 들어 5라는 숫자는 2진수로 표현하면 101입니다. 이를 왼쪽으로 2번 shift 연산을 취해주면 가장 오른쪽 자리수에 0으로 채워지면서 기존의 숫자는 "왼쪽으로 이동"하게 됩니다. 따라서 10100이 되는 것이죠. 그 결과는 20이 될 것입니다.
그와 반대로 오른쪽으로 2번 shift 연산을 취해주면 가장 왼쪽 자리수에 0으로 채워지면서 기존의 숫자는 "오른쪽으로 이동"하게 됩니다. 이때, left shift 연산과는 다르게 가장 작은 자리수보다 더 이동하게 되면 그냥 그 수는 사라지게 됩니다. 5같은 경우에는 101에서 001이 되어 1을 반환하게 되는 것이죠.
np.left_shift(5, 2) # 20
np.right_shift(5, 2) # 1
'Programming > Python' 카테고리의 다른 글
넘파이 알고 쓰자 - 문자열 연산 1 (0) | 2020.09.14 |
---|---|
넘파이 알고 쓰자 - Bit packing & output formatting (0) | 2020.09.13 |
넘파이 알고 쓰자 - 넘파이 원소 재배열하기 (0) | 2020.09.03 |
넘파이 알고 쓰자 - 넘파이의 원소 제거 및 추가(delete, add) (0) | 2020.09.01 |
넘파이 알고 쓰자 - 배열 복붙하기(Tiling) (0) | 2020.08.30 |