Chapter 8: Bit Manipulation

Introduction to Bit Manipulation

Bit manipulation involves the use of bitwise operators to perform operations at the bit level. These operations are efficient and are commonly used in low-level programming, such as device drivers, cryptography, and graphics.

Using std::bitset

#include <bitset>

std::bitset<8> mybitset {}; // 8 bits in size means room for 8 flags

Example of Bit Operations

#include <bitset> // for std::bitset

std::bitset<8> bits{ 0b0000'0101 }; // 8 bits, initial bit pattern 0000 0101
bits.set(3);   // set bit position 3 to 1 (now 0000 1101)
bits.flip(4);  // flip bit 4 (now 0001 1101)
bits.reset(4); // set bit 4 back to 0 (now 0000 1101)

[[maybe_unused]] constexpr int isHungry { 0 };

Bitset Methods

Bitwise Operators

Operator Symbol Form Operation
Left shift << x << y Shifts all bits in x left by y bits
Right shift >> x >> y Shifts all bits in x right by y bits
Bitwise NOT ~ ~x Flips all bits in x
Bitwise AND & x & y AND operation on each bit of x and y
Bitwise OR | x | y OR operation on each bit of x and y
Bitwise XOR ^ x ^ y XOR operation on each bit of x and y

Examples

#include <bitset>
std::bitset<4> x { 0b1100 };

std::cout << (x >> 1) << '\n'; // 0110
std::cout << (x << 1) << '\n'; // 1000
std::cout << (x << 3) << '\n'; // 0000

std::cout << ~std::bitset<4>{ 0b0100 } << '\n';
std::cout << (std::bitset<4>{ 0b0101 } | std::bitset<4>{ 0b0110 } | std::bitset<4>{ 0b0001 }) << '\n'; // 0111
std::cout << (std::bitset<4>{ 0b0101 } & std::bitset<4>{ 0b0110 }) << '\n'; // 0100
std::cout << (std::bitset<4>{ 0b0001 } & std::bitset<4>{ 0b0011 } & std::bitset<4>{ 0b0111 }) << '\n'; // 0001

std::bitset<4> bits {0b0101};
bits >>= 1; // same as bits = bits >> 1

Handling Narrow Integral Types

std::uint8_t cneg { ~c }; // error: narrowing conversion from unsigned int to std::uint8_t
std::uint8_t cneg { static_cast<std::uint8_t>(~c) }; // compiles

Bitwise operators promote operands with narrower integral types to int or unsigned int. Use static_cast to convert the result back to the narrower integral type.

Bit Masks

Bit masks use 0s to mask out bits and 1s to denote bits to be modified.

Examples

// Using binary literals
constexpr std::uint8_t mask0{ 0b0000'0001 }; // bit 0
constexpr std::uint8_t mask1{ 0b0000'0010 }; // bit 1

// Using hexadecimal literals
constexpr std::uint8_t mask0{ 0x01 }; // hex for 0000 0001

// Using bit shifts
constexpr std::uint8_t mask0{ 1 << 0 }; // 0000 0001
constexpr std::uint8_t mask1{ 1 << 1 }; // 0000 0010

Testing and Modifying Bits

Testing a Bit

constexpr std::uint8_t mask0{ 0b0000'0001 }; // bit 0
constexpr std::uint8_t mask1{ 0b0000'0010 }; // bit 1
std::uint8_t flags{ 0b0000'0101 }; // initial flags

std::cout << "bit 0 is " << (static_cast<bool>(flags & mask0) ? "on\n" : "off\n");
std::cout << "bit 1 is " << (static_cast<bool>(flags & mask1) ? "on\n" : "off\n");

Modifying Bits

flags |= mask1; // turn on bit 1
flags |= (mask4 | mask5); // turn on bits 4 and 5

flags &= ~mask2; // turn off bit 2
flags &= static_cast<std::uint8_t>(~mask2); // turn off bit 2
flags &= ~(mask4 | mask5); // turn off bits 4 and 5

flags ^= mask2; // flip bit 2
flags ^= (mask4 | mask5); // flip bits 4 and 5

Using Bitsets for Function Parameters

void someFunction(std::bitset<32> options);

Bit Manipulation in Graphics

constexpr std::uint32_t redBits{ 0xFF000000 };
// Isolate the red component and shift into lower 8 bits
const std::uint8_t red{ static_cast<std::uint8_t>((pixel & redBits) >> 24) };