Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bitwise Operators Cheatsheet (C)

The operators, one bit at a time

OpNameRule (per bit pair a,b)
&AND1 only if both are 1
|OR1 if either is 1
^XOR1 if they differ
~NOTflips the single bit
<<left shiftslides bits toward higher value (×2 per shift)
>>right shiftslides bits toward lower value (÷2 per shift)

What each operator does when combined with a mask

This is the part worth memorizing — what a 1-bit vs a 0-bit in your mask means, per operator:

Opmask bit = 1 means…mask bit = 0 means…
x & maskkeep that bit of x unchangedforce to 0
x | maskforce to 1keep that bit of x unchanged
x ^ maskflip that bit of xkeep that bit of x unchanged
x & ~maskforce to 0keep that bit of x unchanged

Everything below is a consequence of just this table.


Common idioms

x & mask        // TEST/EXTRACT — isolate specific bits, zero the rest
x | mask        // SET — force specific bits to 1, leave rest alone
x & ~mask       // CLEAR — force specific bits to 0, leave rest alone
x ^ mask        // TOGGLE — flip specific bits, leave rest alone
~x              // flip EVERY bit
~x ^ mask       // flip everything, then flip mask's 1-bits back
                //   → net effect: bits under mask=1 stay original,
                //     bits under mask=0 end up flipped
x ^ ~mask       // identical result to ~x ^ mask (see Identities below)
(x >> i) & 1    // test whether bit i specifically is set (0 or 1)
x & (x - 1)     // clear the lowest set bit
x & -x          // isolate the lowest set bit (two's complement trick)
x | (1 << i)    // set bit i
x & ~(1 << i)   // clear bit i
x ^ (1 << i)    // toggle bit i

Masks by width — remember the implicit zero-padding

A literal like 0xFF is widened to match the operand’s width by padding with zeros on the left before the operation runs:

0xFF  on a 32-bit int  →  0x000000FF

That’s why x & 0xFF keeps only the last byte: the mask is 1-bits for the byte you want, and implicit 0-bits for everything above it.

Want to isolateMask
last byte0xFF
last 2 bytes0xFFFF
last nibble (4 bits)0xF
bit i only1 << i
everything except the last byte~0xFF

Shifts

x << k     // multiply by 2^k. Fills k zeros on the right. Drops top k bits.
x >> k     // divide by 2^k (roughly).
  • Right shift on unsigned: always logical — fills with 0s on the left. Well-defined, no ambiguity.
  • Right shift on signed, negative x: implementation-defined — in practice almost universally arithmetic (sign-extends, fills with 1s) on real hardware/compilers, but not guaranteed by the standard.
  • Undefined behavior: shifting by a negative amount, or by an amount >= the width of the type (x << 32 on a 32-bit int). Always guard:
    if (k >= 0 && k < (int)(sizeof(x) * CHAR_BIT)) y = x << k;
    

Useful identities

~x ^ x        == all 1s   (a bit and its complement always differ)
~a ^ b        == a ^ ~b   (complementing either single operand of XOR
                            gives the same result)
a ^ b ^ b     == a         (XOR with the same value twice cancels —
                            this is the basis of the XOR swap)
a & a         == a
a | a         == a
a & ~a        == 0
a | ~a        == all 1s
~(~x)         == x
(x & ~y) | (~x & y) == x ^ y

Set-operation view (bit vectors as sets)

If a bit vector encodes a set (bit i set ⟺ element i is in the set):

a & b     // intersection
a | b     // union
a ^ b     // symmetric difference (elements in exactly one of the two)
~a        // complement (relative to the full universe of bits)
a & ~b    // set difference (A minus B)

Gotchas

  • Operator precedence: &, |, ^ bind looser than ==/!= and comparisons. if (x & mask == 1) parses as x & (mask == 1) — almost always a bug. Always parenthesize: if ((x & mask) == 1).
  • &&/|| vs &/|: the double-character versions are logical operators (short-circuiting, operate on “truthy/falsy” as a whole, return 0/1) — completely different from the single-character bitwise versions. Easy to typo one for the other.
  • Signed shift of negative numbers: right-shifting a negative signed int is implementation-defined, not guaranteed zero-fill. Cast to unsigned first if you need guaranteed logical-shift behavior.
  • Mask width mismatches: x & 0xFF on a 64-bit x still only keeps the last byte — the mask pads with zeros to match, which is usually what you want, but worth double-checking when mixing types of different widths in one expression.

Quick self-test

Given x = 0x87654321 (32-bit):

ExpressionResultWhy
x & 0xFF0x00000021keep last byte, zero rest
x | 0xFF0x876543FFforce last byte to all 1s
x ^ 0xFF0x876543DEflip only the last byte
~x0x789ABCDEflip everything
~x ^ 0xFF0x789ABC21flip everything, then un-flip last byte
x << 40x76543210shift left 4, drop top nibble, zero-fill right
x >> 4 (unsigned)0x08765432shift right 4, zero-fill left