비트연산
1. 비트연산
비트연산은 컴퓨터 과학에서 기본 연산으로 주로 알고리즘 문제 해결에서 자주 등장하지만, 실무에서는 권한 관리나 시스템 최적화, 네트워킹 등의 특수한 상황에서 중요한 역할을 합니다.
비트연산자는 두 이진수(0과 1로 구성된 숫자)에 대해 비트 단위로 논리 연산을 수행합니다. 이러한 연산자에는 주로 AND(&), OR(|), XOR(^), NOT(~), 비트 시프트(<<, >>) 등이 있습니다.
비트연산자를 사용할 때의 일반적인 흐름은 다음과 같습니다.
- 10진수를 2진수로 변환: 컴퓨터는 내부적으로 모든 데이터를 이진수로 처리하기 때문에, 비트 연산을 수행하기 전에 먼저 관련 데이터를 이진수 형태로 변환합니다.
- 2진수에서 비트연산을 전개: 변환된 이진수에 대해 원하는 비트연산을 수행합니다. 예를 들어, 두 숫자에 대해 비트 AND 연산을 수행하면, 두 이진수 모두에서 1인 비트 위치에서만 결과가 1이 되는 새로운 이진수를 얻게 됩니다.
- 2진수를 10진수로 변환: 연산 결과로 나온 이진수를 다시 10진수로 변환하여 사용합니다. 이 과정은 연산 결과를 사람이 이해하기 쉬운 형태로 만들거나, 다른 10진수 기반의 연산이나 처리와 호환될 수 있도록 합니다.
비트연산의 개념과 흐름을 이해하는 것은 컴퓨터 프로그래밍의 깊이를 더하는 데 중요합니다. 이를 통해 더 효율적이고, 최적화된 코드를 작성할 수 있는 능력을 키울 수 있습니다.
1.1 비트 AND(&) 연산자
비트 and 연산자 &는 두 비트열의 동일한 위치에 있는 비트가 모두 1일 경우에만 결과로 1을 반환하는 연산자입니다. 이 연산자는 주로 비트 레벨에서 데이터의 특정 부분을 마스킹하는데 사용됩니다.
예를 들어, 아래의 예제에서는 두 수 60과 13에 대해 비트 AND 연산을 수행합니다.
a = 60 # 60 = 0011 1100
b = 13 # 13 = 0000 1101
result = a & b # 12 = 0000 1100
print(result) # 출력: 12
이 연산의 계산 과정은 다음과 같습니다:
- 이진수 변환: 먼저, a의 값인 60과 b의 값인 13을 이진수로 변환합니다.
- 60의 이진수:
0011 1100 - 13의 이진수:
0000 1101
- 60의 이진수:
- 비트 AND 연산: 각 위치의 비트를 비교하여 두 비트 모두 1일 경우에만 결과의 해당 위치에 1을 배치합니다.
0011 1100(60의 이진수)0000 1101(13의 이진수)0000 1100(AND 연산 결과)
- 결과의 이진수를 10진수로 변환: 연산 결과인
0000 1100을 10진수로 변환합니다. 이 값은 12입니다.
이처럼 비트 AND 연산자는 각 비트 위치에 대해 논리적 'AND' 연산을 수행하여, 두 입력값이 모두 1인 경우에만 결과로 1을 반환합니다.
위 내용을 코드 블록으로 요약한 것입니다.
# a의 값 60을 2진수로 풀어줍니다.
0011 1100
# b의 값 13을 2진수로 풀어줍니다.
0000 1101
# 위아래로 AND 연산을 수행합니다.
0011 1100
0000 1101
# & 연산의 경우 위와 아래 한짝씩 맞춰 and 연산을 수행합니다.
# 이때 1 = True, 0 = False 입니다.
0011 1100
0000 1101
---------
0000 1100
# 결과값을 다시 10진수로 변환합니다.
12
1.2 비트 OR(|) 연산자
비트 or 연산자 |는 두 비트열의 동일한 위치에 최소 하나의 비트가 1일 경우 결과로 1을 반환합니다. 이 연산자는 특정 비트를 설정하거나 두 값 중 하나라도 1인 비트를 확인할 때 유용합니다.
아래 예시에서는 두 수 60과 13에 대해 비트 OR 연산을 수행합니다.
a = 60 # 60 = 0011 1100
b = 13 # 13 = 0000 1101
result = a | b # 61 = 0011 1101
print(result) # 출력: 61
이 연산의 과정은 다음과 같습니다:
- 이진수 변환: 먼저, a와 b의 값인 60과 13을 이진수로 변환합니다.
- 60의 이진수:
0011 1100 - 13의 이진수:
0000 1101
- 60의 이진수:
- 비트 OR 연산: 각 위치의 비트를 비교하여 두 비트 중 하나라도 1이면 결과의 해당 위치에 1을 배치합니다.
0011 1100(60의 이진수)0000 1101(13의 이진수)0011 1101(OR 연산 결과)
- 결과의 이진수를 10진수로 변환: 연산 결과인
0011 1101을 10진수로 변환합니다. 이 값은 61입니다.
비트 OR 연산자는 데이터의 특정 비트를 '켜기' 위한 설정이나, 여러 조건 중 하나라도 충족하는지 확인할 때 주로 사용됩니다. 예를 들어, 사용자 권한 설정에서 여러 권한 중 하나라도 부여되어야 하는 경우 비트 OR 연산을 통해 이를 쉽게 구현할 수 있습니다.
위 내용을 코드 블록으로 요약한 것입니다.
# a의 값 60을 2진수로 풀어줍니다.
0011 1100
# b의 값 13을 2진수로 풀어줍니다.
0000 1101
# 위아래로 붙여보겠습니다.
0011 1100
0000 1101
# 연산의 경우 위와 아래 한짝씩 맞춰 or 비교를 해봅니다.
# 이때 1 = True, 0 = False 입니다.
0011 1100
0000 1101
---------
0011 1101
# 결과값을 다시 10진수로 변환해줍니다.
61
1.3 비트 XOR(^) 연산자
비트 xor 연산자 ^는 두 비트열의 동일한 위치에 있는 비트가 서로 다를 경우에만 1을 반환합니다. 이 연산자는 비교되는 두 값이 다를 때 사용되며, 암호화, 오류 검출, 값의 토글 등 다양한 상황에서 사용됩니다.
다음은 두 수 60과 13에 대해 비트 XOR 연산을 수행하는 예시입니다.
a = 60 # 60 = 0011 1100
b = 13 # 13 = 0000 1101
result = a ^ b # 49 = 0011 0001
print(result) # 출력: 49
이 연산의 과정은 다음과 같습니다:
- 이진수 변환: 먼저, a와 b의 값인 60과 13을 이진수로 변환합니다.
- 60의 이진수:
0011 1100 - 13의 이진수:
0000 1101
- 60의 이진수:
- 비트 XOR 연산: 각 위치의 비트를 비교하여 서로 다를 경우(하나는 1, 다른 하나는 0) 결과의 해당 위치에 1을 배치합니다.
0011 1100(60의 이진수)0000 1101(13의 이진수)0011 0001(XOR 연산 결과)
- 결과의 이진수를 10진수로 변환: 연산 결과인
0011 0001을 10진수로 변환합니다. 이 값은 49입니다.
비트 XOR 연산자는 두 값이 서로 다른 경우를 찾을 때 사용됩니다. 예를 들어, 두 데이터 집합에서 변경된 부분을 찾거나, 데이터를 암호화 및 복호화하는 데 사용할 수 있습니다. 또한, 같은 값을 두 번 XOR 연산하면 원래 값으로 돌아오는 성질을 가지고 있어, 데이터의 일시적인 저장과 복원에도 사용됩니다.
위 내용을 코드 블록으로 요약한 것입니다.
# a의 값 60을 2진수로 풀어줍니다.
0011 1100
# b의 값 13을 2진수로 풀어줍니다.
0000 1101
# 위아래로 붙여보겠습니다.
0011 1100
0000 1101
# 연산의 경우 위와 아래 한짝씩 맞춰 xor 비교를 해봅니다.
# 이때 1 = True, 0 = False 입니다.
0011 1100
0000 1101
---------
0011 0001
# 결과값을 다시 10진수로 변환해줍니다.
49
1.4 비트 NOT(~) 연산자
비트 not 연산자 ~는 주어진 비트열의 모든 비트를 반전시키는 연산자입니다. 이 연산자는 1을 0으로, 0을 1로 바꿉니다. 비트 NOT 연산은 종종 데이터의 비트 패턴을 전환하거나, 특정 비트를 '끄는' 데 사용됩니다.
아래 예시에서는 수 60에 대해 비트 NOT 연산을 수행합니다.
a = 60 # 60 = 0011 1100
result = ~a # -61 = 1100 0011
print(result) # 출력: -61
이 연산의 과정은 다음과 같습니다:
- 이진수 변환: 먼저, a의 값인 60을 이진수로 변환합니다.
- 60의 이진수:
0011 1100
- 60의 이진수:
- 비트 NOT 연산: 모든 비트를 반전시킵니다. 1은 0으로, 0은 1로 바뀝니다.
0011 1100(60의 이진수)-------1100 0011(NOT 연산 결과)
- 결과의 이진수를 10진수로 변환: 연산 결과인
1100 0011을 10진수로 변환합니다. 이 경우, 결과는 -61입니다.
여기서 중요한 점은, 대부분의 컴퓨터 시스템에서 정수는 2의 보수 형태로 표현된다는 것입니다. 따라서, 비트 NOT 연산 후의 결과는 원래 수의 음의 값에서 1을 뺀 값이 됩니다. 예를 들어, ~60의 결과는 -61이 됩니다. 이는 60을 2진수로 표현하고 모든 비트를 반전시킨 후, 이 결과를 2의 보수 형태로 음의 정수로 해석하기 때문입니다.
위 내용을 코드 블록으로 요약한 것입니다.
# a의 값 60을 2진수로 풀어줍니다.
0011 1100
# 연산의 경우 부정을 취합니다.
0011 1100
----
1100 0011
# 결과값을 다시 10진수로 변환해줍니다.
-61
1.5 비트 Shift 연산자
비트 Shift 연산자는 비트열을 왼쪽 또는 오른쪽으로 이동시키는 연산자입니다. 이 연산자는 데이터의 이진 표현을 효율적으로 조작하고, 곱셈 및 나눗셈 연산을 빠르게 수행하는 데 유용합니다.
- 왼쪽 시프트 연산자(<<): 비트열을 왼쪽으로 이동시키며, 오른쪽 끝에 빈 자리는 0으로 채워집니다. 왼쪽 시프트 연산은 주어진 비트 수만큼 2의 거듭제곱을 곱하는 것과 동일합니다.
- 오른쪽 시프트 연산자(>>): 비트열을 오른쪽으로 이동시키며, 왼쪽 끝에 빈 자리는 0(부호가 없는 정수) 또는 부호 비트(부호가 있는 정수)로 채워집니다. 오른쪽 시프트 연산은 주어진 비트 수만큼 2의 거듭제곱으로 나누는 것과 동일합니다.
a = 60 # 60 = 0011 1100
result = a << 2 # 240 = 1111 0000
print(result) # 출력: 240
# 비트열을 왼쪽으로 이동시키며, 빈 자리는 0으로 채웁니다.
result = a >> 2 # 15 = 0000 1111
print(result) # 출력: 15
# 비트열을 오른쪽으로 이동시키며, 빈 자리는 0으로 채웁니다.
이제, 숫자 7에 대해 왼쪽 Shift 연산을 수행하는 예를 살펴보겠습니다.
입력
a = 7 # 7 = 0111
b = 2
print(a << b) # 28 = 1 1100
계산 과정은 다음과 같습니다:
- 이진수 변환: a의 값인 7을 이진수로 변환합니다.
- 7의 이진수:
0111
- 7의 이진수:
- Shift 연산: a의 비트열을 왼쪽으로 b(2)만큼 이동시킵니다. 오른쪽 끝의 빈 자리는 0으로 채워집니다.
0111(7의 이진수)1 1100(왼쪽 Shift 연산 결과)
- 결과의 이진수를 10진수로 변환: 연산 결과인
1 1100을 10진수로 변환합니다. 이 값은 28입니다.
위 내용을 코드 블록으로 요약한 것입니다.
# a의 값 7을 2진수로 바꿉니다.
0111
# b 만큼 이동할 것이기 때문에 b는 바꾸지 않음
# a의 값들(집합)을 왼쪽으로 b만큼 이동시키고 뒤에 0을 붙임
011100
# 결과 값을 10진수로 다시 변환합니다.
28
1.6 2의 보수
2의 보수는 컴퓨터 시스템에서 음수를 표현하는 데 주로 사용되는 방법입니다. 이 방식을 사용하면 덧셈, 뺄셈과 같은 연산이 간단해지며, 음수와 양수 사이의 전환도 쉽게 할 수 있습니다.
예를 들어, 숫자 40의 2의 보수를 구하는 과정은 다음과 같습니다.
a = 40
~a #bit NOT(2의 보수)
101000
#비트단위를 뒤짚었습니다.
010111
#비트단위를 뒤짚고 +1했습니다.
011000
101000
011000
000000 #자기 자신와 보수의 값을 더하면 값은 0이 나옵니다.
#1. 0을 2개로 표현하지 않아도 됩니다.
#2. 값을 더했을 경우 올림수 하나만 버리면 됩니다.
1의 보수는 비트를 완전히 뒤집는 방법입니다. 하지만 이 방법을 쓰면 00000000과 11111111이 각각 +0과 -0이 되어 0이 두 개 생기는 문제가 발생합니다. 그래서 대부분의 현대 컴퓨터 시스템은 2의 보수 방식을 사용합니다. 어떤 수의 음수를 만들려면 비트를 모두 뒤집은 뒤 1을 더하면 됩니다.
이 방식의 장점은 두 가지입니다. 첫째, 0의 표현이 하나뿐입니다. 둘째, 절댓값이 같은 음수와 양수를 더하면 맨 앞으로 넘어간 자리올림 하나만 버려도 결과가 정확히 0이 됩니다. 덕분에 뺄셈을 위한 별도의 회로 없이 덧셈 회로만으로 뺄셈까지 처리할 수 있습니다.