Bit Manipulation

Bitwise AND, OR, and XOR

Bitwise operators work on the binary representations of their operands. They compare corresponding bits of the integers, then set a new bit in each resulting integer if that bit is 1 or 0 based on a rule you set forth for that specific bit's pair.

All three of these bitwise operators form the basis of most bit level programming, and after you understand their rules, it becomes very easy to read and write masks and flags.

When you perform a bitwise AND, it sets a bit in the result only when the bits match exactly and the bits are both a 1; otherwise, the bit in the result will always be a 0.

// Use binary notation for this integer literal
unsigned int x{0b1101};
unsigned int y{0b1011};
unsigned int z{x & y};

Working left to right on our bits:

  • Position 3: a 1 ANDed with another 1 will always yield a 1

  • Position 2: a 1 ANDed with a 0 will always yield a 0

  • Position 1: a 0 ANDed with a 1 will always yield a 0

  • Position 0: a 1 ANDed with another 1 will always yield a 1

This operator is very commonly used to clear specific bits or to test to see if a specific bit is set. When you write out value & mask, every bit position where the mask holds a 0 forces the corresponding bit in the value to become a 0 as well; any bit positions where the mask holds a 1 leave the original bit's position unchanged.

The bitwise OR operator sets a bit if that bit is 1 for either or both of the operands. The bit is only a 0 if that bit is a 0 in both of the operands.

unsigned int x{0b1100};
unsigned int y{0b1010};
// Set each bit that appears in either operand
unsigned int z{x | y};

Bit by bit:

  • Position 3: a 1 ORed with a 1 will always yield a 1

  • Position 2: a 1 ORed with a 0 will always yield a 1

  • Position 1: a 0 ORed with a 1 will always yield a 1

  • Position 0: a 0 ORed with a 0 will always yield a 0

This operator is generally used to turn bits on when you do not care what state the bit is in right now. value | mask forces every bit position held by a 1 to become a 1 in the result, while leaving any 0s unchanged.

The XOR operator is also known as exclusive or. It is the same as an AND except where it does not necessarily have two 1s. Instead, it only sets a bit if that bit is a 1 in either of the operands, but not both.

unsigned int x{0b1100};
unsigned int y{0b1010};
// Set each bit where the operands differ
unsigned int z{x ^ y};

Bit by bit:

  • Position 3: a 1 XORed with a 1 will always yield a 0

  • Position 2: a 1 XORed with a 0 will always yield a 1

  • Position 1: a 0 XORed with a 1 will always yield a 1

  • Position 0: a 0 XORed with a 0 will always yield a 0

An interesting side effect of XOR is that applying the same mask twice returns the original value, making it the ideal operator for flipping bits.

value ^ mask flips every bit where the mask holds a 1, but leaves any bits unchanged where the mask holds a 0. If you do it again, the prior bit pattern returns.

A mask is simply an integer used to access individual bit positions.

The three operators:

  • Use an AND operator with a 0 in the bit position that you want to clear

  • Use an OR operator with a 1 in the bit position that you want to set

  • Use an XOR operator with a 1 in the bit position that you want to flip

Since all three operators run independently on every bit, you don't need to worry about accidentally changing bits you are not targeting, or the previous value of that bit.

This concept will usually feel much more natural than converting it all back and forth from decimal every single time. The patterns will still be clear when you know the bit by bit operation of the three operators.

Bitwise AND only sets bits that are 1 in both numbers.

Bitwise OR sets a bit if it is 1 in either of the numbers.

Bitwise XOR sets a bit only if the bit differs between the two operands.

All these three operators, when combined with a mask, can turn, reset, and test any number.

#include <iostream>

int main()
{
    unsigned int a{0b1101};
    unsigned int b{0b1011};

    std::cout << "a & b = " << (a & b) << '\n';
    std::cout << "a | b = " << (a | b) << '\n';
    std::cout << "a ^ b = " << (a ^ b) << '\n';

    unsigned int flags{0b0000};

    flags = flags | 0b1010;
    std::cout << "After setting bits 1 and 3: " << flags << '\n';

    // Clear the bits selected by this mask
    flags = flags & ~0b1000;
    std::cout << "After clearing bit 3: " << flags << '\n';

    flags = flags ^ 0b0010;
    std::cout << "After toggling bit 1: " << flags << '\n';

    bool bit1{(flags & 0b0010) != 0};
    std::cout << "Bit 1 is set: " << bit1 << '\n';

    flags = flags ^ 0b0010;
    std::cout << "After second toggle of bit 1: " << flags << '\n';
}

These operators are simple when seen, and all bit manipulation code in C++ comes down to these three operators at the bit level.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.