프로그래밍/개념 뽀개기

✨ 비트 연산자 (Bitwise Operators)란 무엇인가?

lamarcK 2025. 3. 29. 12:38

비트 연산자(Bitwise Operators)는 컴퓨터 프로그래밍에서 숫자를 우리가 일상적으로 사용하는 10진수가 아닌, 컴퓨터가 내부적으로 데이터를 처리하는 방식인 2진수(Binary)의 비트(Bit) 단위로 직접 조작하고 연산하는 데 사용되는 특별한 종류의 연산자(Operator)들을 의미한다.

⚙️ 핵심 원리: 비트(Bit) 수준에서의 연산

  • 2진 표현: 비트 연산자는 피연산자(연산 대상이 되는 값, 주로 숫자)를 먼저 2진수 형태(0과 1로 이루어진 시퀀스, JavaScript에서는 보통 부호 있는 32비트 정수로 간주)로 변환한다.
  • 비트 단위 조작: 그런 다음, 이 2진수의 각 자리에 있는 비트(0 또는 1)들을 직접 다루어 연산을 수행한다.
    • & (AND), | (OR), ^ (XOR) 등은 두 피연산자의 같은 자릿수 비트끼리 논리 연산을 수행한다.
    • ~ (NOT)은 한 피연산자의 모든 비트를 반전시킨다.
    • << (Left Shift), >> (Right Shift), >>> (Zero-fill Right Shift) 등은 비트 시퀀스 전체를 지정된 수만큼 왼쪽 또는 오른쪽으로 이동시킨다.
  • 결과 반환: 연산이 완료되면, 결과로 나온 2진수 비트 패턴을 다시 일반적인 숫자 값으로 변환하여 반환한다.

💡 주요 목적 및 사용처

비트 연산자는 일반적인 산술 연산(+, -, *, /)이나 논리 연산(&&, ||)과는 다른 목적을 위해 사용된다.

  1. 저수준(Low-level) 데이터 제어: 메모리의 특정 비트를 직접 조작해야 하는 경우, 하드웨어 장치와 통신하거나 특정 파일 형식을 읽고 쓰는 등 시스템 프로그래밍에 가까운 작업에서 필요할 수 있다.
  2. 성능 최적화 (특정 경우): 곱셈/나눗셈을 2의 거듭제곱으로 수행할 때 시프트 연산(<<, >>)을 사용하거나, 홀수/짝수 판별에 & 연산을 사용하는 등 일부 연산은 산술 연산보다 CPU 수준에서 더 빠를 수 있다 (단, 현대 컴파일러/엔진의 최적화로 인해 그 차이가 줄어들고 있으며, 가독성을 해칠 수 있어 남용은 좋지 않다).
  3. 데이터 압축 및 플래그(Flags) 관리: 하나의 숫자 변수 내에 여러 개의 상태 정보(플래그)를 비트별로 저장하고 관리할 수 있다. 예를 들어, 8개의 서로 다른 ON/OFF 상태를 8비트(1바이트) 정수 하나에 저장하고, |로 상태를 켜고, &로 상태를 확인하는 식이다.
  4. 마스킹(Masking): 특정 비트들만 추출하거나(AND), 특정 비트들만 변경하고 싶을 때 사용한다.
  5. 특정 알고리즘 구현: 그래픽 처리, 암호화, 해시 함수 등 특정 알고리즘들은 내부적으로 비트 연산을 효율적으로 활용하는 경우가 많다.

⚠️ 일반적인 개발에서의 사용 빈도

웹 개발과 같은 고수준 애플리케이션 개발에서는 비트 연산자를 직접 사용할 일이 상대적으로 적다. 산술 연산자, 논리 연산자, 비교 연산자 등이 훨씬 더 자주 사용된다. 하지만 비트 연산자의 작동 원리를 이해하면 컴퓨터가 데이터를 처리하는 방식에 대한 이해를 높일 수 있으며, 특정 라이브러리나 프레임워크의 내부 코드, 또는 성능 최적화가 중요한 코드를 이해하는 데 도움이 될 수 있다.

요약: 비트 연산자는 숫자를 2진 비트로 간주하여, 각 비트 단위에서 직접 논리 연산이나 시프트 연산을 수행하는 특별한 연산자 그룹이다. 주로 저수준 제어, 특정 성능 최적화, 플래그 관리 등의 목적으로 사용된다.

 

비트 연산자의 비교 방식

비트 연산자(Bitwise Operators), 특히 두 개의 피연산자를 사용하는 & (AND), | (OR), ^ (XOR)는 각 비트의 자릿수 별로 비교하여 연산을 수행한다.

🔢 자릿수별 비교 방식

  1. 2진수 변환: 먼저 연산에 참여하는 두 숫자를 2진수로 변환한다.
  2. 자릿수 정렬: 두 2진수의 각 자릿수를 맞추어 정렬한다 (오른쪽 끝, 즉 최하위 비트(LSB)부터 시작).
  3. 각 자릿수별 연산: 같은 자릿수에 있는 비트끼리 해당 비트 연산자(&, |, 또는 ^)의 규칙에 따라 계산한다.
  4. 결과 비트 생성: 각 자릿수별 계산 결과를 모아 최종 결과 값의 2진수 표현을 만든다.

  9 & 5 비트 연산 결과

숫자 9   ->  1001 (2진수)
숫자 5   ->  0101 (2진수) - 자릿수 맞춤

          ↓ ↓ ↓ ↓  (각 자릿수별로 비교)
          1 0 0 1
        & 0 1 0 1
        ----------
결과 비트:  0 0 0 1  (각 자리의 두 비트가 모두 1일 때만 결과가 1)

최종 결과: 0001 (2진수) -> 1 (10진수)
  • 가장 오른쪽 (2^0) 자릿수의 1과 1을 & 연산한다.
  • 두 번째 (2^1) 자릿수의 0과 0을 & 연산한다.
  • 세 번째 (2^2) 자릿수의 0과 1을 & 연산한다.
  • 네 번째 (2^3) 자릿수의 1과 0을 & 연산한다.

✨ 13 & 11 비트 연산 결과

   1 1 0 1   (13의 2진수)
&  1 0 1 1   (11의 2진수)
-----------
   1 0 0 1   (결과 2진수)
  • 가장 오른쪽 (1의 자리): 1 & 1 = 1
  • 두 번째 자리 (2의 자리): 0 & 1 = 0
  • 세 번째 자리 (4의 자리): 1 & 0 = 0
  • 네 번째 자리 (8의 자리): 1 & 1 = 1

✅ 결과 2진수를 10진수로 변환

연산 결과로 얻은 1001 (2진수)를 다시 10진수로 변환한다.

  • 1001 (2진수) = (1 * 2³) + (0 * 2²) + (0 * 2¹) + (1 * 2⁰) = (1 * 8) + (0 * 4) + (0 * 2) + (1 * 1) = 8 + 0 + 0 + 1 = 9 (10진수)

결론적으로, 13 & 11의 비트 연산 결과는 9이다.

 

이처럼 각 자릿수(position)는 독립적으로, 해당 위치에 있는 두 피연산자의 비트만을 사용하여 계산된다.

 

기존적으로 2진수는 0아니면 1이기 때문에 비트 연산자를 "각 자릿수의 참/거짓(True/False)을 따져서 결과를 다시 비트 자릿수로 반환하는 연산자"라고 이해하는 것은 개념 이해에 도움이 된다.

 

 

각 자릿수의 참/거짓: 2진수에서 각 자릿수의 비트 1을 "참(True)" 또는 "켜짐(On)" 상태로, 비트 0을 "거짓(False)" 또는 "꺼짐(Off)" 상태로 생각할 수 있다.

자릿수별 논리 연산: 비트 연산자 &(AND), |(OR), ^(XOR)는 두 숫자의 같은 자릿수에 있는 비트들의 참/거짓 상태를 바탕으로 논리 연산을 수행한다.

  • & (AND): 두 자릿수가 모두 참(1)**일 때만 결과 자릿수가 참(1)이 된다.
  • | (OR): 두 자릿수 중 하나라도 참(1)**이면 결과 자릿수가 참(1)이 된다.
  • ^ (XOR): 두 자릿수가 서로 다를 때 (하나는 참(1), 하나는 거짓(0))만 결과 자릿수가 참(1)이 된다.

결과를 비트 자릿수로 반환: 이 자릿수별 논리 연산의 결과(각 자릿수가 최종적으로 1 또는 0이 됨)를 모아서 새로운 2진수 값을 만들어낸다. 이 2진수 값이 바로 비트 연산의 최종 결과이다. (물론 JavaScript에서는 이 최종 2진수 결과가 보통 10진수 숫자로 표시된다.)

 


다른 연산자:

  • 비트 NOT (~): 피연산자 하나만 받으며, 해당 숫자의 각 자릿수 비트를 개별적으로 반전시킨다.
  • 시프트 연산자 (<<, >>, >>>): 비트들을 다른 자릿수로 이동시키는 방식으로 동작한다.

결론적으로, 비트 연산은 숫자를 비트(0과 1)의 배열로 보고, **같은 위치(자릿수)**에 있는 비트들 간의 관계를 기반으로 계산하는 방식이다.