How to Use Bitwise Shift Operators in C
Bitwise shift operations in C# allow developers to manipulate
individual bits within integral data types by moving them to the left or
right. This article covers the mechanics of the left-shift
(<<), right-shift (>>), and
unsigned right-shift (>>>) operators, explains how
integer promotion and count masking affect outcomes, and demonstrates
practical code examples for common programming scenarios.
The Left Shift Operator
(<<)
The left shift operator (<<) shifts the bits of
the left-hand operand to the left by the number of positions specified
by the right-hand operand. High-order bits outside the range of the
result type are discarded, and empty low-order bit positions are filled
with zeros.
Mathematically, shifting an integer left by \(n\) positions is equivalent to multiplying that number by \(2^n\), provided no overflow occurs.
int value = 5; // Binary: 0000 0101
int result = value << 2; // Binary: 0001 0100 (Decimal: 20)The Right Shift Operator
(>>)
The right shift operator (>>) shifts the bits of
the first operand to the right. The behavior of the high-order bits
depends on the type of the value being shifted:
- Unsigned Types (
uint,ulong): Performs a logical shift, filling empty high-order positions with zeros. - Signed Types (
int,long): Performs an arithmetic shift, preserving the sign bit. If the number is positive, empty positions are filled with zeros; if negative, empty positions are filled with ones.
Shifting a value right by \(n\) positions effectively divides the value by \(2^n\), rounded down toward negative infinity.
int positive = 20; // Binary: 0001 0100
int posResult = positive >> 2; // Binary: 0000 0101 (Decimal: 5)
int negative = -20;
int negResult = negative >> 2; // Decimal: -5The Unsigned Right Shift
Operator (>>>)
Introduced in C# 11 (.NET 7), the unsigned right shift operator
(>>>) performs a logical right shift regardless of
whether the operand is signed or unsigned. It always fills the vacated
high-order bit positions with zeros.
int negative = -20;
int result = negative >>> 2;
// Using >> preserves the sign bit (results in a negative number).
// Using >>> inserts leading zeros (results in a large positive number).Type Promotion and Supported Types
Bitwise shift operators are predefined only for int,
uint, long, and ulong.
When applying shift operators to smaller integral types like
byte, sbyte, short, or
ushort, the runtime automatically promotes them to
int before evaluating the expression. Consequently, the
result must be explicitly cast back to the smaller type if needed:
byte b = 0b_0000_1100;
byte shifted = (byte)(b << 2);Shift Count Masking
C# limits the effective shift count to prevent unnecessary or undefined processor cycles. The shift count is masked using only the lowest-order bits:
- For 32-bit types (
int,uint), only the low-order 5 bits of the count are used (count & 0x1F). The effective shift count ranges from0to31. - For 64-bit types (
long,ulong), only the low-order 6 bits are used (count & 0x3F). The effective shift count ranges from0to63.
Attempting to shift an int by 32 does not clear all
bits; instead, value << 32 evaluates as
value << 0, returning the original value.
int number = 10;
int shifted = number << 32; // Equivalent to number << 0; output is 10Compound Assignment Operators
C# provides shorthand compound assignment operators for all shift operations:
<<=(Left shift assignment)>>=(Right shift assignment)>>>=(Unsigned right shift assignment)
int mask = 1;
mask <<= 4; // mask is now 16 (0b_0001_0000)Common Applications
Bitwise shifts are essential for low-level programming tasks:
- Bit Masking and Flags: Setting, clearing, or reading individual bits within a status register or enum.
- Data Serialization: Packing multiple smaller values into a single integer, or unpacking bytes from network streams.
- Performance Optimization: Replacing expensive multiplication or division operations with single-cycle bit shifts when working with powers of two.