The bitwise NOT operator flips every bit in its operand. So 0 becomes 1 and 1 becomes 0. It is a unary operator, represented by the symbol ~.
unsigned int value{0b0000'1010};
// Invert every bit in this value
unsigned int flipped{~value};
The only thing you may not expect about ~ is how it behaves when there are bits that don't contain the value you are using.
The NOT operator flips all bits, including the higher zero bits.
For example, when using unsigned int, which we are assuming to be 32 bits, a small number like 5 may use only 3 bits, such as binary 00...0101. Applying ~ flips these bits to binary 11...1010, which has a value of approximately 4 billion.
This inverted value is often called the one's complement of the original value. For an n bit unsigned integer, ~x is defined as (2^n - 1) - x. This is why ~0u is the largest possible unsigned value, because each of the 0 bits will have been flipped to 1.
// The suffix makes this integer literal unsigned
unsigned int all_bits_set{~0u};
Because all the bits are inverted, and it is usually only a few lower bits that you really care about, the fact that high bits that were initially 0 have been flipped to 1 is problematic. In this case, another bitmask is often applied to clear these higher bits.
unsigned int value{0b0000'1010};
// Keep only bit positions set in both operands
unsigned int low_eight_bits{(~value) & 0xFFu};
One of the most common uses of ~ is for creating a mask that clears some bits. To do this, we can first create a value which has a 1 only at the positions where we want the original value to be cleared. We can then apply ~ to this value.
unsigned int flags{0b0000'1111};
// Shift one into position to build a single-bit mask
unsigned int mask{~(1u << 3)};
This clears the target bits to 0 and flips the others to 1.
We can then AND this value with the original value, and every bit at the target position will be zero, while the others will be unchanged.
flags = flags & mask;
To clear multiple bits, you can apply the same pattern. First, create a value that has 1s where bits should be cleared, then invert it, then bitwise AND it with the value you are working on.
unsigned int flags{0b1111'1111};
unsigned int bits_to_clear{0b0000'0011};
// Clear the bits selected by this mask
flags = flags & ~bits_to_clear;
When we use it on signed integers, it simply flips every bit as well, but the result is interpreted as a signed number. If we use a two's complement system and invert a small positive signed integer, we will normally get a negative number, since the sign bit has been flipped to 1.
// Use binary notation for this integer literal
int value{0b0000'1010};
int flipped{~value};
It can be simpler to work with bit manipulation using unsigned integers because the bit interpretation is straightforward.
The number of bits that are inverted depends on the type after integer promotion, which for char and short means it will usually be an int. If the result is stored into a smaller integer, then higher bits will be truncated.
unsigned char small{0b0000'1010};
int promoted_result{~small};
// Perform this conversion explicitly
unsigned char stored_back{static_cast<unsigned char>(~small)};
#include <iostream>
int main()
{
unsigned int value{0b0000'1010};
std::cout << "Original unsigned: " << value << '\n';
std::cout << "~value at full width: " << ~value << '\n';
std::cout << "~value masked to 8 bits: " << ((~value) & 0xFFu) << '\n';
unsigned int flags{0b0000'1111};
std::cout << "Flags before clearing: " << flags << '\n';
flags = flags & ~(1u << 2);
std::cout << "Flags after clearing bit 2: " << flags << '\n';
unsigned int mask{~0b0011u};
std::cout << "Mask for clearing the lowest two bits: " << (mask & 0xFFu) << '\n';
int signed_value{0b0000'1010};
std::cout << "Original signed value: " << signed_value << '\n';
std::cout << "~signed_value: " << ~signed_value << '\n';
}
The bitwise NOT operator always flips every bit in the promoted type, not only the visible low bits of the value.
It is especially useful for creating masks that contain 0s in the positions that should be cleared and 1s everywhere else.
Using unsigned integers usually makes the resulting bit patterns easier to understand because they are interpreted as nonnegative binary values.
0 comments
No comments yet. Be the first.