The bitwise operators
C++ provides 6 bit manipulation operators, often called bitwise operators:
Operator | Symbol | Form | The operation returns a value where: |
---|---|---|---|
left shift | << | x << n | the bits from x are shifted left by n positions, new bits are 0 . |
right shift | >> | x >> n | the bits from x are shifted right by n positions, new bits are 0 . |
bitwise NOT | ~ | ~x | each bit from x is flipped. |
bitwise AND | & | x & y | each bit is set when both corresponding bits in x and y are 1 . |
bitwise OR | | | x | y | each bit is set when either corresponding bit in x and y is 1 . |
bitwise XOR | ^ | x ^ y | each bit is set when the corresponding bits in x and y are different. |
These are non-modifying operators (they do not modify their operands).
Author’s note
In the following examples, we will largely be working with 4-bit binary values. This is for the sake of convenience and keeping the examples simple. In actual programs, the number of bits used is based on the size of the object (e.g. a 2 byte object would store 16 bits).
To improve readability, we may also omit the 0b
prefix for binary literals outside of code examples (e.g. instead of 0b0101
, we may opt to use 0101
).
The bitwise operators are defined for integral types and std::bitset
. We’ll use std::bitset
in our examples because it’s easier to print the output in binary.
Avoid using the bitwise operators with signed integral operands, as many operators will return implementation-defined results prior to C++20 or have other potential gotchas that are easily avoided by using unsigned operands (or std::bitset
).
Best practice
To avoid surprises, use the bitwise operators with unsigned integral operands or std::bitset
.
Bitwise left shift (<<) and bitwise right shift (>>) operators
The bitwise left shift (<<) operator shifts bits to the left. The left operand is an expression that provides the initial bit sequence, and the right operand is an integer number that specifies the number of bit-positions to move the bits over by. For example, when we write x << 2
, we are saying “produce a value where the bits from x
have been moved 2 positions to the left.”
The left operand is not modified, and new bits shifted in from the right size are 0
.
Here are some examples of left shifting the bit sequence 0011
:
0011 \<\< 1 is 0110 0011 \<\< 2 is 1100 0011 \<\< 3 is 1000
Note that in the third case, we shifted a 1
bit off the end of the number! Bits that are shifted off the end of the bit sequence are lost forever.
The bitwise right shift (>>) operator works similarly but shifts bits to the right.
Here are some examples of right shifting the bit sequence 1100
:
1100 />/> 1 is 0110 1100 />/> 2 is 0011 1100 />/> 3 is 0001
Note that in the third case we shifted a bit off the right end of the number, so it is lost.
Let’s do an example in C++ that you can compile and run:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> x { 0b1100 };
std::cout << x << '\n';
std::cout << (x >> 1) << '\n'; // shift right by 1, yielding 0110
std::cout << (x << 1) << '\n'; // shift left by 1, yielding 1000
return 0;
}
This prints:
1100 0110 1000
For advanced readers
Bit-shifting in C++ is endian-agnostic. Left-shift is always towards the most significant bit, and right-shift towards the least significant bit.
What!? Aren’t operator<< and operator>> used for input and output?
They sure are.
Programs today typically do not make much use of the bitwise left and right shift operators to shift bits. Rather, the bitwise left shift operator is more often used with std::cout
(or other output stream objects) to output text. Consider the following program:
#include <bitset>
#include <iostream>
int main()
{
unsigned int x { 0b0100 };
x = x << 1; // use operator<< for left shift
std::cout << std::bitset<4>{ x } << '\n'; // use operator<< for output
return 0;
}
This program prints:
1000
In the above program, how does operator<<
know to shift bits in one case and output x
in another case? The answer is that it looks at the type of the operands. If the left operand is an integral type, then operator<<
knows to do its usual bit-shifting behavior. If the left operand is a an output stream object like std::cout
, then it knows it should do output instead.
The same applies for operator>>
.
Related content
This ability for operators to change their behavior based on the type of the arguments leverages a feature called operator overloading, which we introduce later in lesson 13.5 -- Introduction to overloading the I/O operators.
Note that if you’re using operator<<
for both output and left shift, parenthesization is required to use for left-shifting:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> x{ 0b0110 };
std::cout << x << 1 << '\n'; // print value of x (0110), then 1
std::cout << (x << 1) << '\n'; // print x left shifted by 1 (1100)
return 0;
}
This prints:
01101 1100
The first line prints the value of x
(0110
), and then the literal 1
. The second line prints the value of x
left-shifted by 1
(1100
).
Bitwise NOT
The bitwise NOT operator (~) is conceptually straightforward: It simply flips each bit from a 0
to a 1
, or vice versa.
~0011 is 1100 ~0000 0100 is 1111 1011
For advanced readers
When interpreted as an integer, the number of bits in the result of a bitwise NOT affects the value produced.
The following program illustrates this:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> b4{ 0b100 }; // b4 is 0100
std::bitset<8> b8{ 0b100 }; // b8 is 0000 0100
std::cout << "Initial values:\n";
std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
std::cout << "Values: " << b4.to_ulong() << ' ' << b8.to_ulong() << "\n\n";
b4 = ~b4; // flip b4 to 1011
b8 = ~b8; // flip b8 to 1111 1011
std::cout << "After bitwise NOT:\n";
std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
std::cout << "Values: " << b4.to_ulong() << ' ' << b8.to_ulong() << '\n';
return 0;
}
This prints:
Initial values: Bits: 0100 00000100 Values: 4 4 After bitwise NOT: Bits: 1011 11111011 Values: 11 251
Initially, b4
and b8
are both set to 0b100
. When padded with leading zeros, b4
ends up as 0100
and b8
as 00000100
, which is printed on the next line.
We then use the to_ulong()
member function to interpret the value of the bits, interpreted as a long
integer. You can see that both b4
and b8
print the value 4
. Despite the different number of bits, they both represent the same value. This is because leading zero bits contribute no value to the interpreted integer.
Then we use bitwise NOT to flip the bits of each, so b4
now has bits 1011
and b8
now has bits 1111 1011
. When printed as an integer, this prints the values 11
and 251
. As you can see, these values are no longer identical. This is because leading ones do contribute value to the interpreted integer, and b8
has more leading ones than b4
.
Bitwise OR
Bitwise OR (|) works much like its logical OR counterpart. If you remember, logical OR evaluates to true
(1
) if either of the operands are true
, otherwise it evaluates to false
(0
).
However, whereas logical OR is applied to the entire operand (to produce a single true or false result), bitwise OR is applied to each pair of bits in the operands (to produce a single true or false result for each bit).
Let’s illustrate this with an example. Consider the expression 0b0101 | 0b0110
.
Tip
To do any binary bitwise operation by hand, it is easiest to line the two operands up like this:
0 1 0 1 OR (or whatever bitwise operation you are doing) 0 1 1 0
Then, apply the operation to each column of bits, and write the result underneath.
In the first column, 0
OR 0
is 0
, so we put a 0 underneath the line.
0 1 0 1 OR 0 1 1 0 ------- 0
Second column, 1
OR 1
is 1
. Third column 0
or 1
is 1
. And fourth column, 1
or 0
is 1
.
0 1 0 1 OR 0 1 1 0 ------- 0 1 1 1
Our result is 0111
binary.
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0101 } | std::bitset<4>{ 0b0110 }) << '\n';
return 0;
}
This prints:
0111
We can do the same thing to compound bitwise OR expressions, such as 0b0111 | 0b0011 | 0b0001
. If any of the bits in a column are 1
, the result of that column is 1
:
0 1 1 1 OR 0 0 1 1 OR 0 0 0 1 -------- 0 1 1 1
Here’s code for the above:
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0111 } | std::bitset<4>{ 0b0011 } | std::bitset<4>{ 0b0001 }) << '\n';
return 0;
}
This prints:
0111
Bitwise AND
Bitwise AND (&) works similarly to the above, except it uses AND logic instead of OR logic. That is, for each pair of bits in the operands, Bitwise AND sets the resulting bit to true
(1
) if both paired bits are 1
, and false
(0
) otherwise.
Consider the expression 0b0101 & 0b0110
. Lining each of the bits up and applying bitwise AND to each column of bits:
0 1 0 1 AND 0 1 1 0 -------- 0 1 0 0
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0101 } & std::bitset<4>{ 0b0110 }) << '\n';
return 0;
}
This prints:
0100
Similarly, we can do the same thing to compound bitwise AND expressions, such as 0b0001 & 0b0011 & 0b0111
. If all of the bits in a column are 1
, the result of that column is 1
.
0 0 0 1 AND 0 0 1 1 AND 0 1 1 1 -------- 0 0 0 1
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0001 } & std::bitset<4>{ 0b0011 } & std::bitset<4>{ 0b0111 }) << '\n';
return 0;
}
This prints:
0001
Bitwise XOR
The last operator is the bitwise XOR (^), also known as exclusive or.
For each pair of bits in the operands, Bitwise XOR sets the resulting bit to true
(1
) when exactly one of the paired bits is 1
, and false
(0
) otherwise. Put another way, Bitwise XOR sets the resulting bit to true
when the paired bits are different (one is a 0
and the other a 1
).
Consider the expression 0b0110 ^ 0b0011
:
0 1 1 0 XOR 0 0 1 1 ------- 0 1 0 1
It is also possible to evaluate compound XOR expression column style, such as 0b0001 ^ 0b0011 ^ 0b0111
. If there are an even number of 1
bits in a column, the result is 0
. If there are an odd number of 1
bits in a column, the result is 1
.
0 0 0 1 XOR 0 0 1 1 XOR 0 1 1 1 -------- 0 1 0 1
Bitwise assignment operators
Similar to the arithmetic assignment operators, C++ provides bitwise assignment operators. These do modify the left operand.
Operator | Symbol | Form | The operation modifies the left operand where: |
---|---|---|---|
left shift | << | x <<= n | the bits in x are shifted left by n positions, new bits are 0 . |
right shift | >> | x >>= n | the bits in x are shifted right by n positions, new bits are 0 . |
bitwise AND | & | x &= y | each bit is set when both corresponding bits in x and y are 1 . |
bitwise OR | | | x |= y | each bit is set when either corresponding bit in x and y is 1 . |
bitwise XOR | ^ | x ^= y | each bit is set when the corresponding bits in x and y are different. |
For example, instead of writing x = x >> 1;
, you can write x >>= 1;
.
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> bits { 0b0100 };
bits >>= 1;
std::cout << bits << '\n';
return 0;
}
This program prints:
0010
As an aside…
There is no bitwise NOT assignment operator. This is because the other bitwise operators are binary, but bitwise NOT is unary (so what would go on the right-hand side of a ~=
operator?). If you want to flip all of the bits of an object, you can use normal assignment: x = ~x;
Bitwise operators perform integral promotion on smaller integral types Advanced
If the operand(s) of a bitwise operator are an integral type smaller than an int
, those operands will be promoted (converted) to int
or unsigned int
, and the result returned will also be an int
or unsigned int
. For example, if our operands are unsigned short
, they will be promoted (converted) to unsigned int
, and the result of the operation will be returned as an unsigned int
.
In many cases, this won’t matter.
Related content
We cover integral promotion in lesson 10.2 -- Floating-point and integral promotion.
However, when using bitwise operators on integral types narrower than int
or unsigned int
, there are two cases to watch out for:
operator~
andoperator<<
are width-sensitive and may produce different results depending on the width of the operand.- Initializing or assigning the result to a variable of the smaller integral type is a narrowing conversion (since converting an
int
orunsigned int
to a smaller integral type may result in data loss). This is disallowed in list initialization, and your compiler may or may not complain about a narrowing assignment.
The following program exhibits these issues (assuming 32-bit ints):
#include <bitset>
#include <cstdint>
#include <iostream>
int main()
{
std::uint8_t c { 0b00001111 };
std::cout << std::bitset<32>(~c) << '\n'; // incorrect: prints 11111111111111111111111111110000
std::cout << std::bitset<32>(c << 6) << '\n'; // incorrect: prints 0000000000000000001111000000
std::uint8_t cneg { ~c }; // error: narrowing conversion from unsigned int to std::uint8_t
c = ~c; // possible warning: narrowing conversion from unsigned int to std::uint8_t
return 0;
}
These issues can be addressed by using static_cast
to convert the result of your bitwise operation back to the narrower integral type. The following program produces the correct results:
#include <bitset>
#include <cstdint>
#include <iostream>
int main()
{
std::uint8_t c { 0b00001111 };
std::cout << std::bitset<32>(static_cast<std::uint8_t>(~c)) << '\n'; // correct: prints 00000000000000000000000011110000
std::cout << std::bitset<32>(static_cast<std::uint8_t>(c << 6)) << '\n'; // correct: prints 0000000000000000000011000000
std::uint8_t cneg { static_cast<std::uint8_t>(~c) }; // compiles
c = static_cast<std::uint8_t>(~c); // no warning
return 0;
}
Warning
Bitwise operators will promote operands with narrower integral types to int
or unsigned int
.
operator~
and operator<<
are width-sensitive and may produce different results depending on the width of the operand. static_cast
the result of such bitwise operations back to the narrower integral type before using to ensure correct results.
Best practice
Avoid bit shifting on integral types smaller than int
whenever possible.
Summary
Summarizing how to evaluate bitwise operations utilizing the column method:
When evaluating bitwise OR, if any bit in a column is 1, the result for that column is 1.
When evaluating bitwise AND, if all bits in a column are 1, the result for that column is 1.
When evaluating bitwise XOR, if there are an odd number of 1 bits in a column, the result for that column is 1.
In the next lesson, we’ll explore how these operators can be used in conjunction with bit masks to facilitate bit manipulation.
Quiz time
Question #1
a) What does 0110 >> 2 evaluate to in binary?
b) What does the following evaluate to in binary: 0011 | 0101?
c) What does the following evaluate to in binary: 0011 & 0101?
d) What does the following evaluate to in binary (0011 | 0101) & 1001?
Question #2
A bitwise rotation is like a bitwise shift, except that any bits shifted off one end are added back to the other end. For example 0b1001 << 1
would be 0b0010
, but a left rotate by 1 would result in 0b0011
instead. Implement a function that does a left rotate on a std::bitset<4>
. For this one, it’s okay to use test() and set().
The following code should execute:
#include <bitset>
#include <iostream>
// "rotl" stands for "rotate left"
std::bitset<4> rotl(std::bitset<4> bits)
{
// Your code here
}
int main()
{
std::bitset<4> bits1{ 0b0001 };
std::cout << rotl(bits1) << '\n';
std::bitset<4> bits2{ 0b1001 };
std::cout << rotl(bits2) << '\n';
return 0;
}
and print the following:
0010 0011
Question #3
Extra credit: Redo quiz #2 but don’t use the test and set functions (use bitwise operators).