비트 및 시프트 연산자(C# 참조)

비트 및 시프트 연산자에는 단항 비트 보수, 이진 왼쪽 및 오른쪽 시프트, 부호 없는 오른쪽 시프트, 이진 논리 AND, OR 및 배타적 OR 연산자가 포함됩니다. 이러한 피연산자는 정수 숫자 형식 또는 char 형식의 피연산자를 사용합니다.

이러한 연산자는 int, uint, longulong 형식에 대해 정의되어 있습니다. 두 피연산자가 모두 다른 정수 형식(sbyte, byte, short, ushort 또는 char)인 경우, 해당 값은 작업의 결과 형식이기도 한 int 유형으로 변환됩니다. 피연산자가 다른 정수 형식인 경우 해당 값은 가장 가까운 정수 형식으로 변환됩니다. 자세한 내용은 C# 언어 사양숫자 승격 섹션을 참조하세요. 복합 연산자(예: >>=)는 인수를 int로 변환하지 않거나 결과 형식이 int입니다.

bool 유형의 피연산자에 대한 &, |^ 연산자도 정의되어 있습니다. 자세한 내용은 부울 논리 연산자를 참조하세요.

비트 및 시프트 작업으로 인해 오버플로가 발생하지 않고 Checked 및 Unchecked 컨텍스트에서 동일한 결과가 생성되지 않습니다.

비트 보수 연산자 ~

~ 연산자는 각 비트를 반대로 하여 해당 피연산자의 비트 보수를 생성합니다.

uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
Console.WriteLine(Convert.ToString(b, toBase: 2));
// Output:
// 11110000111100001111000011110011

~ 기호를 사용하여 종료자를 선언할 수도 있습니다. 자세한 내용은 종료자를 참조하세요.

왼쪽 시프트 연산자 <<

<< 연산자는 왼쪽 피연산자를 오른쪽 피연산자로 정의된 비트 수만큼 왼쪽으로 이동합니다. 오른쪽 피연산자가 시프트 수를 정의하는 방법에 대한 자세한 내용은 시프트 연산자의 시프트 수 섹션을 참조하세요.

왼쪽 시프트 연산은 결과 형식의 범위를 벗어나는 상위 비트를 삭제하고 다음 예제와 같이 빈 하위 비트 위치를 0으로 설정합니다.

uint x = 0b_1100_1001_0000_0000_0000_0000_0001_0001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2)}");

uint y = x << 4;
Console.WriteLine($"After:  {Convert.ToString(y, toBase: 2)}");
// Output:
// Before: 11001001000000000000000000010001
// After:  10010000000000000000000100010000

시프트 연산자는 int, uint, longulong 유형에 대해서만 정의되므로 작업 결과에는 항상 32비트 이상이 포함됩니다. 왼쪽 피연산자가 다른 정수 형식(sbyte, byte, short, ushort 또는 char)인 경우, 다음 예제와 같이 해당 값이 int 유형으로 변환됩니다.

byte a = 0b_1111_0001;

var b = a << 8;
Console.WriteLine(b.GetType());
Console.WriteLine($"Shifted byte: {Convert.ToString(b, toBase: 2)}");
// Output:
// System.Int32
// Shifted byte: 1111000100000000

오른쪽 시프트 연산자 >>

>> 연산자는 왼쪽 피연산자를 오른쪽 피연산자로 정의된 비트 수만큼 오른쪽으로 이동합니다. 오른쪽 피연산자가 시프트 수를 정의하는 방법에 대한 자세한 내용은 시프트 연산자의 시프트 수 섹션을 참조하세요.

오른쪽 시프트 연산은 다음 예제와 같이 하위 비트를 삭제합니다.

uint x = 0b_1001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2), 4}");

uint y = x >> 2;
Console.WriteLine($"After:  {Convert.ToString(y, toBase: 2).PadLeft(4, '0'), 4}");
// Output:
// Before: 1001
// After:  0010

빈 상위 공백 비트 위치는 다음과 같이 왼쪽 피연산자 형식에 따라 설정됩니다.

  • 왼쪽 피연산자가 int 또는 long 형식인 경우 오른쪽 시프트 연산자는 산술 시프트를 수행합니다. 왼쪽 피연산자의 최상위 비트(부호 비트) 값이 빈 상위 비트 위치로 전파됩니다. 즉, 빈 상위 비트 위치는 왼쪽 피연산자가 음수가 아닌 경우 0으로 설정되고, 음수인 경우 1로 설정됩니다.

    int a = int.MinValue;
    Console.WriteLine($"Before: {Convert.ToString(a, toBase: 2)}");
    
    int b = a >> 3;
    Console.WriteLine($"After:  {Convert.ToString(b, toBase: 2)}");
    // Output:
    // Before: 10000000000000000000000000000000
    // After:  11110000000000000000000000000000
    
  • 왼쪽 피연산자가 uint 또는 ulong 형식이면 오른쪽 시프트 피연산자는 논리적 시프트를 수행합니다. 빈 상위 비트 위치가 항상 0으로 설정됩니다.

    uint c = 0b_1000_0000_0000_0000_0000_0000_0000_0000;
    Console.WriteLine($"Before: {Convert.ToString(c, toBase: 2), 32}");
    
    uint d = c >> 3;
    Console.WriteLine($"After:  {Convert.ToString(d, toBase: 2).PadLeft(32, '0'), 32}");
    // Output:
    // Before: 10000000000000000000000000000000
    // After:  00010000000000000000000000000000
    

참고 항목

부호 있는 정수 형식의 피연산자에 대해 논리적 시프트를 수행하려면 부호 없는 오른쪽 시프트 연산자를 사용합니다. 이는 왼쪽 피연산자를 부호 없는 형식으로 캐스팅한 다음 시프트 연산의 결과를 다시 부호 있는 형식으로 캐스팅하는 것보다 기본 설정됩니다.

부호 없는 오른쪽 시프트 연산자 >>>

C# 11 이상에서 사용할 수 있는 >>> 연산자는 오른쪽 피연산자에 정의된 비트 수만큼 왼쪽 피연산자를 오른쪽으로 이동합니다. 오른쪽 피연산자가 시프트 수를 정의하는 방법에 대한 자세한 내용은 시프트 연산자의 시프트 수 섹션을 참조하세요.

>>> 연산자는 항상 논리적 시프트를 수행합니다. 즉, 왼쪽 피연산자의 형식에 관계없이 상위 빈 비트 위치는 항상 0으로 설정됩니다. >> 연산자는 왼쪽 피연산자가 부호 있는 유형인 경우 산술 시프트를 수행합니다(즉, 가장 중요한 비트의 값이 상위 빈 비트 위치로 전파됨). 다음 예에서는 음수 왼쪽 피연산자에 대한 >> 연산자와 >>> 연산자 간의 차이점을 보여 줍니다.

int x = -8;
Console.WriteLine($"Before:    {x,11}, hex: {x,8:x}, binary: {Convert.ToString(x, toBase: 2), 32}");

int y = x >> 2;
Console.WriteLine($"After  >>: {y,11}, hex: {y,8:x}, binary: {Convert.ToString(y, toBase: 2), 32}");

int z = x >>> 2;
Console.WriteLine($"After >>>: {z,11}, hex: {z,8:x}, binary: {Convert.ToString(z, toBase: 2).PadLeft(32, '0'), 32}");
// Output:
// Before:             -8, hex: fffffff8, binary: 11111111111111111111111111111000
// After  >>:          -2, hex: fffffffe, binary: 11111111111111111111111111111110
// After >>>:  1073741822, hex: 3ffffffe, binary: 00111111111111111111111111111110

논리 AND 연산자 &

& 연산자는 해당 정수 피연산자의 비트 논리 AND를 컴퓨팅합니다.

uint a = 0b_1111_1000;
uint b = 0b_1001_1101;
uint c = a & b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10011000

bool 피연산자의 경우 & 연산자는 피연산자의 논리 AND를 컴퓨팅합니다. 단항 & 연산자는 address-of 연산자입니다.

논리 배타적 OR 연산자 ^

^ 연산자는 해당 정수 피연산자의 비트 논리 XOR이라고도 하는 비트 논리 배타적 OR을 컴퓨팅합니다.

uint a = 0b_1111_1000;
uint b = 0b_0001_1100;
uint c = a ^ b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 11100100

bool 피연산자의 경우 ^ 연산자는 피연산자의 논리 배타적 OR을 컴퓨팅합니다.

논리 OR 연산자 |

| 연산자는 해당 정수 피연산자의 비트 논리 OR을 컴퓨팅합니다.

uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10110001

bool 피연산자의 경우 | 연산자는 피연산자의 논리 OR을 컴퓨팅합니다.

복합 할당

이진 연산자(op)의 경우 양식의 복합 할당 식

x op= y

위의 식은 아래의 식과 동일합니다.

x = x op y

단, x가 한 번만 계산됩니다.

다음 예제에서는 비트 및 시프트 연산자를 사용하는 복합 할당의 사용법을 보여줍니다.

uint INITIAL_VALUE = 0b_1111_1000;

uint a = INITIAL_VALUE;
a &= 0b_1001_1101; 
Display(a);  // output: 10011000

a = INITIAL_VALUE;
a |= 0b_0011_0001; 
Display(a);  // output: 11111001

a = INITIAL_VALUE;
a ^= 0b_1000_0000;
Display(a);  // output: 01111000

a = INITIAL_VALUE;
a <<= 2;
Display(a);  // output: 1111100000

a = INITIAL_VALUE;
a >>= 4;
Display(a);  // output: 00001111

a = INITIAL_VALUE;
a >>>= 4;
Display(a);  // output: 00001111

void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2).PadLeft(8, '0'), 8}");

숫자 승격으로 인해 op 연산의 결과가 암시적으로 xT 형식으로 변환되지 못할 수 있습니다. 이 경우 op가 미리 정의된 연산자이고 연산의 결과가 명시적으로 xT 형식으로 변환 가능하다면 x op= y 양식의 복합 할당 식이 x = (T)(x op y)에 해당합니다. 단 x는 한 번만 평가됩니다. 다음 예제에서는 해당 동작을 보여줍니다.

byte x = 0b_1111_0001;

int b = x << 8;
Console.WriteLine($"{Convert.ToString(b, toBase: 2)}");  // output: 1111000100000000

x <<= 8;
Console.WriteLine(x);  // output: 0

연산자 우선 순위

다음 목록에서는 비트 및 시프트 연산자를 가장 높은 우선순위부터 가장 낮은 것으로 정렬합니다.

  • 비트 보수 연산자 ~
  • 시프트 연산자 <<, >>>>>
  • 논리 AND 연산자 &
  • 논리 배타적 OR 연산자 ^
  • 논리 OR 연산자 |

괄호(())를 사용하여 연산자 우선 순위에 따라 주어진 평가 순서를 변경합니다.

uint a = 0b_1101;
uint b = 0b_1001;
uint c = 0b_1010;

uint d1 = a | b & c;
Display(d1);  // output: 1101

uint d2 = (a | b) & c;
Display(d2);  // output: 1000

void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2), 4}");

우선 순위 수준에 따라 정렬된 전체 연산자 목록은 C# 연산자 문서의 연산자 우선 순위 섹션을 참조하세요.

시프트 연산자의 시프트 수

기본 제공 시트프 연산자 <<, >>>>>의 경우 오른쪽 피연산자의 형식은 int이거나 int로의 미리 정의된 암시적 숫자 변환이 있는 형식이어야 합니다.

x << count, x >> countx >>> count식의 경우 실제 시프트 수는 다음과 같이 x 형식에 따라 달라집니다.

  • x의 형식이 int 또는 uint이면 시프트 수는 오른쪽 피연산자의 하위 5비트로 정의됩니다. 즉, 시프트 수는 count & 0x1F(또는 count & 0b_1_1111)에서 계산됩니다.

  • x의 형식이 long 또는 ulong이면 시프트 수는 오른쪽 피연산자의 하위 6비트로 정의됩니다. 즉, 시프트 수는 count & 0x3F(또는 count & 0b_11_1111)에서 계산됩니다.

다음 예제에서는 해당 동작을 보여줍니다.

int count1 = 0b_0000_0001;
int count2 = 0b_1110_0001;

int a = 0b_0001;
Console.WriteLine($"{a} << {count1} is {a << count1}; {a} << {count2} is {a << count2}");
// Output:
// 1 << 1 is 2; 1 << 225 is 2

int b = 0b_0100;
Console.WriteLine($"{b} >> {count1} is {b >> count1}; {b} >> {count2} is {b >> count2}");
// Output:
// 4 >> 1 is 2; 4 >> 225 is 2

int count = -31;
int c = 0b_0001;
Console.WriteLine($"{c} << {count} is {c << count}");
// Output:
// 1 << -31 is 2

참고 항목

앞의 예제에서 볼 수 있듯이 오른쪽 피연산자의 값이 왼쪽 피연산자의 비트 수보다 크면 시프트 연산의 결과가 0이 아닐 수 있습니다.

열거형 논리 연산자

~, &, |^ 연산자도 열거형 형식에 대해 지원됩니다. 동일한 열거형 형식의 피연산자인 경우, 기본 정수 형식의 해당 값에 대해 논리 연산을 수행됩니다. 예를 들어 기본 형식이 U인 열거형 형식 Txy에 대해 x & y 식은 (T)((U)x & (U)y) 식과 동일한 결과를 생성합니다.

일반적으로 Flags 특성으로 정의된 열거형 형식을 가진 비트 논리 연산자를 사용합니다. 자세한 내용은 열거형 형식 문서의 비트 플래그로서 열거형 형식을 참조하세요.

연산자 오버로드 가능성

사용자 정의 형식은 ~, <<, >>, >>>, &, |^ 연산자를 오버로드할 수 있습니다. 이항 연산자가 오버로드되면 해당하는 복합 대입 연산자도 암시적으로 오버로드됩니다. 사용자 정의 형식은 복합 대입 연산자를 명시적으로 오버로드할 수 없습니다.

사용자 정의 형식 T<<, >> 또는 >>> 연산자를 오버로드하는 경우 왼쪽 피연산자의 형식은 T여야 합니다. C# 10 이하에서는 오른쪽 피연산자의 형식이 int이어야 합니다. C# 11부터 오버로드된 시프트 연산자의 오른쪽 피연산자의 형식은 any일 수 있습니다.

C# 언어 사양

자세한 내용은 C# 언어 사양의 다음 섹션을 참조하세요.

참고 항목