Bit Operators
Remarks#
Bit shift operations are not portable across all processor architectures, different processors can have different bit-widths. In other words, if you wrote
int a = ~0;
int b = a << 1;
This value would be different on a 64 bit machine vs. on a 32 bit machine, or from an x86 based processor to a PIC based processor.
Endian-ness does not need to be taken into account for the bit wise operations themselves, that is, the right shift (>>
) will shift the bits towards the least significant bit and an XOR will perform an exclusive or on the bits. Endian-ness only needs to be taken into account with the data itself, that is, if endian-ness is a concern for your application, it’s a concern regardless of bit wise operations.
& - bitwise AND
int a = 6; // 0110b (0x06)
int b = 10; // 1010b (0x0A)
int c = a & b; // 0010b (0x02)
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Output
a = 6, b = 10, c = 2
Why
A bit wise AND
operates on the bit level and uses the following Boolean truth table:
TRUE AND TRUE = TRUE
TRUE AND FALSE = FALSE
FALSE AND FALSE = FALSE
When the binary value for a
(0110
) and the binary value for b
(1010
) are AND
‘ed together we get the binary value of 0010
:
int a = 0 1 1 0
int b = 1 0 1 0 &
---------
int c = 0 0 1 0
The bit wise AND does not change the value of the original values unless specifically assigned to using the bit wise assignment compound operator &=
:
int a = 5; // 0101b (0x05)
a &= 10; // a = 0101b & 1010b
| - bitwise OR
int a = 5; // 0101b (0x05)
int b = 12; // 1100b (0x0C)
int c = a | b; // 1101b (0x0D)
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Output
a = 5, b = 12, c = 13
Why
A bit wise OR
operates on the bit level and uses the following Boolean truth table:
true OR true = true
true OR false = true
false OR false = false
When the binary value for a
(0101
) and the binary value for b
(1100
) are OR
‘ed together we get the binary value of 1101
:
int a = 0 1 0 1
int b = 1 1 0 0 |
---------
int c = 1 1 0 1
The bit wise OR does not change the value of the original values unless specifically assigned to using the bit wise assignment compound operator |=
:
int a = 5; // 0101b (0x05)
a |= 12; // a = 0101b | 1101b
^ - bitwise XOR (exclusive OR)
int a = 5; // 0101b (0x05)
int b = 9; // 1001b (0x09)
int c = a ^ b; // 1100b (0x0C)
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Output
a = 5, b = 9, c = 12
Why
A bit wise XOR
(exclusive or) operates on the bit level and uses the following Boolean truth table:
true OR true = false
true OR false = true
false OR false = false
Notice that with an XOR operation true OR true = false
where as with operations true AND/OR true = true
, hence the exclusive nature of the XOR operation.
Using this, when the binary value for a
(0101
) and the binary value for b
(1001
) are XOR
‘ed together we get the binary value of 1100
:
int a = 0 1 0 1
int b = 1 0 0 1 ^
---------
int c = 1 1 0 0
The bit wise XOR does not change the value of the original values unless specifically assigned to using the bit wise assignment compound operator ^=
:
int a = 5; // 0101b (0x05)
a ^= 9; // a = 0101b ^ 1001b
The bit wise XOR can be utilized in many ways and is often utilized in bit mask operations for encryption and compression.
Note: The following example is often shown as an example of a nice trick. But should not be used in production code (there are better ways std::swap()
to achieve the same result).
You can also utilize an XOR operation to swap two variables without a temporary:
int a = 42;
int b = 64;
// XOR swap
a ^= b;
b ^= a;
a ^= b;
std::cout << "a = " << a << ", b = " << b << "\n";
To productionalize this you need to add a check to make sure it can be used.
void doXORSwap(int& a, int& b)
{
// Need to add a check to make sure you are not swapping the same
// variable with itself. Otherwise it will zero the value.
if (&a != &b)
{
// XOR swap
a ^= b;
b ^= a;
a ^= b;
}
}
So though it looks like a nice trick in isolation it is not useful in real code. xor is not a base logical operation,but a combination of others: a^c=~(a&c)&(a|c)
also in 2015+ compilers variables may be assigned as binary:
int cn=0b0111;
~ - bitwise NOT (unary complement)
unsigned char a = 234; // 1110 1010b (0xEA)
unsigned char b = ~a; // 0001 0101b (0x15)
std::cout << "a = " << static_cast<int>(a) <<
", b = " << static_cast<int>(b) << std::endl;
Output
a = 234, b = 21
Why
A bit wise NOT
(unary complement) operates on the bit level and simply flips each bit. If it’s a 1
, it’s changed to a 0
, if it’s a 0
, it’s changed to a 1
. The bit wise NOT has the same effect as XOR’ing a value against the max value for a specific type:
unsigned char a = 234; // 1110 1010b (0xEA)
unsigned char b = ~a; // 0001 0101b (0x15)
unsigned char c = a ^ ~0;
The bit wise NOT can also be a convenient way to check the maximum value for a specific integral type:
unsigned int i = ~0;
unsigned char c = ~0;
std::cout << "max uint = " << i << std::endl <<
"max uchar = " << static_cast<short>(c) << std::endl;
The bit wise NOT does not change the value of the original value and does not have a compound assignment operator, so you can not do a ~= 10
for example.
The bit wise NOT (~
) should not be confused with the logical NOT (!
); where a bit wise NOT will flip each bit, a logical NOT will use the whole value to do its operation on, in other words (!1) != (~1)
<< - left shift
int a = 1; // 0001b
int b = a << 1; // 0010b
std::cout << "a = " << a << ", b = " << b << std::endl;
Output
a = 1, b = 2
Why
The left bit wise shift will shift the bits of the left hand value (a
) the number specified on the right (1
), essentially padding the least significant bits with 0’s, so shifting the value of 5
(binary 0000 0101
) to the left 4 times (e.g. 5 << 4
) will yield the value of 80
(binary 0101 0000
). You might note that shifting a value to the left 1 time is also the same as multiplying the value by 2, example:
int a = 7;
while (a < 200) {
std::cout << "a = " << a << std::endl;
a <<= 1;
}
a = 7;
while (a < 200) {
std::cout << "a = " << a << std::endl;
a *= 2;
}
But it should be noted that the left shift operation will shift all bits to the left, including the sign bit, example:
int a = 2147483647; // 0111 1111 1111 1111 1111 1111 1111 1111
int b = a << 1; // 1111 1111 1111 1111 1111 1111 1111 1110
std::cout << "a = " << a << ", b = " << b << std::endl;
Possible output: a = 2147483647, b = -2
While some compilers will yield results that seem expected, it should be noted that if you left shift a signed number so that the sign bit is affected, the result is undefined. It is also undefined if the number of bits you wish to shift by is a negative number or is larger than the number of bits the type on the left can hold, example:
int a = 1;
int b = a << -1; // undefined behavior
char c = a << 20; // undefined behavior
The bit wise left shift does not change the value of the original values unless specifically assigned to using the bit wise assignment compound operator <<=
:
int a = 5; // 0101b
a <<= 1; // a = a << 1;
>> - right shift
int a = 2; // 0010b
int b = a >> 1; // 0001b
std::cout << "a = " << a << ", b = " << b << std::endl;
Output
a = 2, b = 1
Why
The right bit wise shift will shift the bits of the left hand value (a
) the number specified on the right (1
); it should be noted that while the operation of a right shift is standard, what happens to the bits of a right shift on a signed negative number is implementation defined and thus cannot be guaranteed to be portable, example:
int a = -2;
int b = a >> 1; // the value of b will be depend on the compiler
It is also undefined if the number of bits you wish to shift by is a negative number, example:
int a = 1;
int b = a >> -1; // undefined behavior
The bit wise right shift does not change the value of the original values unless specifically assigned to using the bit wise assignment compound operator >>=
:
int a = 2; // 0010b
a >>= 1; // a = a >> 1;