Left and right bit shifts shift the bits of an integer a specific number of places one way or the other. <<, the left shift operator, shifts bits to the high end of the value. >>, the right shift operator, shifts bits to the low end of the value.
Vacated bit positions are filled in according to easily defined rules, and any bits that have been shifted off the ends are discarded.
Both operators can be used with the most certainty on unsigned operands. Unsigned operands can be treated purely as bit patterns, and the operation is the same as multiplication or division by a power of two.
In a left shift, every bit is shifted toward the more significant end. Zeros fill in at the less significant positions. Each shift position multiplies the value by two, provided that no 1 bits are lost from the high end.
// Use binary notation for this integer literal
unsigned int value{0b0001};
unsigned int result{value << 3};
Shifting 0001 three places to the left results in 1000.
The logical and mathematical effect is simple. Shifting left once for unsigned values is the same numerically as multiplying by two, as long as the result remains representable by the type.
Multiplying by eight produces the same result as shifting left three places.
Shifting by zero does not change the value of the expression.
If you shift by an amount greater than or equal to the width of the promoted left operand, the behavior is undefined.
In a right shift, every bit is shifted toward the less significant end.
For unsigned types, zeros fill in at the more significant positions.
Every time you shift right by one position on an unsigned type, you divide by two, discarding the fraction.
unsigned int value{0b1000};
// Shift the bit pattern right by this amount
unsigned int result{value >> 2};
Shifting 1000 two places to the right results in 0010. The 1 shifts down, zeros come in from the high end, and the low order bits are shifted off the end and lost.
For negative signed values, right shift behavior can be harder to reason about because the high positions may be filled with copies of the sign bit. This is a significant cause of signed integers being less convenient to use for bit manipulation tasks, and is another reason to strongly prefer unsigned integers whenever working with explicit bit patterns.
Shifts may be used to place a single 1 in any desired bit position.
// Shift one into position to build a single-bit mask
unsigned int bit3{1u << 3};
unsigned int bit0{1u << 0};
1u << 3 creates a value with only bit 3 set. 1u << 0 is simply 1.
This mask can be used with AND, OR, and XOR to test or change a specific bit within another value.
One can OR together a number of shifted ones to form masks which target multiple bits simultaneously.
// Set each bit that appears in either operand
unsigned int mask{(1u << 1) | (1u << 4)};
Parentheses are not strictly needed around each individual shift operation, but help to keep the mask easy to visually recognize.
The right operand of a shift cannot be negative; otherwise, the behavior is undefined.
It also cannot be greater than or equal to the bit width of the promoted left operand. A value of this magnitude also yields undefined behavior.
Remember also, in chained shifts, that at each step the intermediate expression remains limited to the size of its type. Anything that shifts off the left or right during one step cannot be brought back by shifting in the opposite direction afterward.
Here is a left shift and a right shift shown in action and the concept they imply about the manipulation of bit patterns, illustrated by using them to make masks that affect bit positions.
A left shift shifts bits toward the more significant end and zeros fill in at the low end. For unsigned integer values, this is generally equivalent to multiplication by two, as long as the result remains representable.
A mask is generated by shifting a one leftward.
A right shift shifts bits toward the less significant end and zeros fill in at the high end. For unsigned integer values, this is generally equivalent to division by two rounded down through integer division.
#include <iostream>
int main()
{
unsigned int value{1u};
std::cout << "1 << 0 = " << (value << 0) << '\n';
std::cout << "1 << 1 = " << (value << 1) << '\n';
std::cout << "1 << 2 = " << (value << 2) << '\n';
std::cout << "1 << 3 = " << (value << 3) << '\n';
unsigned int pattern{0b11000};
std::cout << "0b11000 >> 1 = " << (pattern >> 1) << '\n';
std::cout << "0b11000 >> 2 = " << (pattern >> 2) << '\n';
std::cout << "0b11000 >> 3 = " << (pattern >> 3) << '\n';
unsigned int mask{(1u << 2) | (1u << 5)};
std::cout << "Mask with bits 2 and 5: " << mask << '\n';
unsigned int flags{0u};
flags = flags | (1u << 4);
std::cout << "Flags after setting bit 4: " << flags << '\n';
// Keep only bit positions set in both operands
bool test{(flags & (1u << 4)) != 0};
std::cout << "Bit 4 is set: " << test << '\n';
unsigned int x{0b1000};
std::cout << "0b1000 << 1 = " << (x << 1) << '\n';
std::cout << "0b1000 >> 1 = " << (x >> 1) << '\n';
}
Left and right bit shifts offer tremendous control over bits within an integer type, so long as the values involved in the shifts are unsigned numbers within the legal range for the shift operation. The shift count must remain within the type's bit width and must not be negative.
0 comments
No comments yet. Be the first.