Skip to content
Feb 28

Bitwise Operations

MT
Mindli Team

AI-Generated Content

Bitwise Operations

Bitwise operations are the atomic instructions of low-level computation, allowing you to manipulate the individual binary digits—or bits—that make up data. While high-level languages abstract away these details, direct bit manipulation remains essential for systems programming, embedded development, and writing highly optimized algorithms. Mastering these operations unlocks a deeper understanding of how computers actually process information and provides elegant solutions to problems involving flags, states, and mathematical efficiency.

Understanding the Binary Playground

Before manipulating bits, you must understand their canvas. At the hardware level, computers store integers in a fixed number of bits, commonly 32 or 64, using a binary representation. Each bit holds a value of 0 or 1. The rightmost bit is the least significant bit (LSB), representing , or 1. The leftmost is the most significant bit (MSB), representing the highest power of two. For example, the decimal number 5 is 0101 in 4-bit binary (). Bitwise operators work on each corresponding pair of bits in these binary representations.

The Core Bitwise Operators

These operators compare or alter bits at identical positions within two numbers (or one, for NOT).

The AND Operator (&) The AND operator returns 1 only if both corresponding bits are 1. Otherwise, it returns 0. Think of it as a strict "both must be true" filter.

    0101 (5)
AND 0011 (3)
    ----
    0001 (1)

A primary use is bit masking, where you use a mask (a number with specific bits set to 1) to isolate, test, or clear bits from another number. For instance, to check if the LSB of a number x is 1 (i.e., if it's odd), you use the mask 1: (x & 1). If the result is 1, the number is odd.

The OR Operator (|) The OR operator returns 1 if at least one of the corresponding bits is 1. It's used to set or combine flags.

    0101 (5)
 OR 0011 (3)
    ----
    0111 (7)

If you want to ensure a specific bit is turned on (set to 1), you OR the number with a mask that has a 1 in that position. For example, to set the third bit (from the right, 0-indexed) of x, you would compute x = x | (1 << 2).

The XOR Operator (^) The exclusive OR (XOR) operator returns 1 only if the corresponding bits are different. It's a "one or the other, but not both" check.

    0101 (5)
XOR 0011 (3)
    ----
    0110 (6)

XOR has unique properties: a number XORed with itself is 0 (x ^ x = 0), and XORing with 0 returns the original number (x ^ 0 = x). This makes it invaluable for toggling bits (flipping 0 to 1 and 1 to 0) and is the foundation of certain encryption and error-checking algorithms.

The NOT Operator (~) The NOT operator is a unary operator that flips every bit in its operand: 1 becomes 0, and 0 becomes 1. This operation is also called the one's complement.

NOT 0101 (5)
    ----
    1010 (10 in unsigned? Caution!)

The result depends on how the number is interpreted (signed vs. unsigned). For a signed 32-bit integer, ~5 equals -6. This is because it inverts all 32 bits, including the sign bit in two's complement representation. Its common use is creating inverted masks.

Shifting Bits: Multiplication and Division by Powers of Two

Shift operators move all bits in a number left or right, filling the vacated positions with zeros (in a logical shift). This is computationally one of the cheapest operations a CPU can perform.

Left Shift (<<) The left shift operator (<<) moves bits to the left. Each left shift by n positions effectively multiplies the number by .

5 << 1  // Binary: 0101 -> 1010, which is 10 (5 * 2^1)
3 << 2  // Binary: 0011 -> 1100, which is 12 (3 * 2^2)

Right Shift (>>) The right shift operator (>>) moves bits to the right. Each right shift by n positions effectively performs integer division by , discarding any remainder.

12 >> 2  // Binary: 1100 -> 0011, which is 3 (12 / 4)
7 >> 1   // Binary: 0111 -> 0011, which is 3 (7 / 2, remainder discarded)

These shifts are the most efficient multiplication/division by powers of two and are often used in performance-critical code.

Applied Bit Manipulation: Flags and Masks

One of the most practical applications is managing a set of boolean flags within a single integer. Instead of using multiple separate variables, you can pack many true/false states into one number, saving memory and allowing for fast, atomic updates.

  1. Define Constants: Each flag is a unique power of two (a single bit set).

const int FLAGA = 1 << 0; // 0001 (1) const int FLAGB = 1 << 1; // 0010 (2) const int FLAG_C = 1 << 2; // 0100 (4)

  1. Setting a Flag (Turn ON a bit): Use the OR operator.

int flags = 0; flags = flags | FLAGB; // flags now has FLAGB set.

  1. Clearing a Flag (Turn OFF a bit): Use AND with the NOT of the mask.

flags = flags & ~FLAGB; // Clears FLAGB.

  1. Checking a Flag (Testing if a bit is ON): Use AND.

if ((flags & FLAGA) != 0) { // FLAGA is set. }

This technique is ubiquitous in systems programming, graphics APIs, and hardware register configuration.

Common Pitfalls

  1. Confusing Bitwise (&, |) with Logical (&&, ||): This is a classic error. Bitwise operators work on integer bits, while logical operators work on boolean truth values. Using & in a conditional like if (x & 1) is correct (it evaluates to an integer). Using if (x && 1) is usually wrong, as it checks if both x and 1 are non-zero, not their bits.
  1. Ignoring Operator Precedence: Bitwise operators have lower precedence than comparison or arithmetic operators. Forgetting parentheses can lead to unexpected results. (flags & MASK) != 0 is very different from flags & MASK != 0. The latter is interpreted as flags & (MASK != 0). Always use explicit parentheses when mixing bitwise operations with others.
  1. Right Shifting Negative Numbers: The behavior of the right shift operator (>>) on signed negative integers is implementation-defined in some languages (like C/C++), often performing an arithmetic shift that preserves the sign bit. For predictable, logical shifts on signed numbers, first cast to an unsigned type or use language-specific guarantees.
  1. Off-by-One Errors with Shifts: Shifting by a number equal to or greater than the bit width of the data type (e.g., shifting a 32-bit int by 32) results in undefined behavior. The operation is not guaranteed to yield zero and may produce compiler or platform-specific results.

Summary

  • Bitwise operations (&, |, ^, ~, <<, >>) perform direct, parallel logic on the binary representations of integers.
  • Bit masking with AND is used to isolate or test bits, while OR is used to set them. XOR toggles bits and has useful mathematical properties.
  • Shift operators provide the most efficient way to multiply or divide integers by powers of two, a fundamental optimization technique.
  • Packing multiple boolean flags into a single integer using powers of two is a memory-efficient pattern common in systems-level code.
  • Always be mindful of operator precedence, the signed/unsigned nature of your data, and shift widths to avoid subtle bugs in your low-level logic.

Write better notes with AI

Mindli helps you capture, organize, and master any subject with AI-powered summaries and flashcards.