← All Posts
DSA · Bit Manipulation· Part 7 of 32

Bit Shifting Deep Dive

Left Shift in Detail

Left shift by $k$ moves every bit $k$ positions to the left. The $k$ vacated positions on the right are filled with 0. Bits that shift beyond the width are lost.

uint8_t x = 0b00110101;   // 53
uint8_t y = x << 2;      // 0b11010100 = 212

// Mathematically: x << k = x × 2^k (mod 2^n for n-bit type)
// 53 × 4 = 212 ✓

// Overflow: if high bits shift out, they are lost
uint8_t z = 0b11000000 << 1;  // 0b10000000 (top bit lost)

▶ Left Shift Visualization

Step through to see what x << k does to an 8-bit value (0b00010110 << 2).

Right Shift: Logical vs Arithmetic

Logical Right Shift

Fills vacated high bits with 0. Always used for unsigned types.

uint8_t x = 0b11010100;   // 212
uint8_t y = x >> 2;      // 0b00110101 = 53

// Mathematically: x >> k = ⌊x / 2^k⌋ for unsigned
// 212 / 4 = 53 ✓

Arithmetic Right Shift

Fills vacated high bits with the sign bit. Used for signed types (implementation-defined in C++, but virtually all compilers do arithmetic shift).

int8_t x = -24;           // 0b11101000
int8_t y = x >> 2;       // 0b11111010 = -6

// The sign bit (1) is replicated into the top positions
// Mathematically: ⌊-24 / 4⌋ = -6 ✓

// But watch out for negative odd numbers:
int8_t a = -7;            // 0b11111001
int8_t b = a >> 1;       // 0b11111100 = -4 (rounds toward -∞, not toward 0!)
// -7 / 2 = -3.5 → arithmetic shift gives -4 (floor)
// But integer division gives -3 (truncate toward zero)
Arithmetic right shift rounds toward negative infinity. Integer division truncates toward zero. For negative numbers, these differ: (-7) >> 1 = -4 but (-7) / 2 = -3. This is a common source of bugs.

Shift Undefined Behavior

// UB 1: Shifting by negative amount
x << -1;    // UNDEFINED

// UB 2: Shifting by >= bit width
uint32_t x = 1;
x << 32;    // UNDEFINED (shift amount must be in [0, 31])

// UB 3: Left-shifting a negative value (until C++20)
int x = -1;
x << 1;     // Undefined before C++20, defined as -2 in C++20

// UB 4: Left-shifting into or past the sign bit (before C++20)
int x = 1;
x << 31;    // UB before C++20 if int is 32-bit (sets sign bit)

Shift-Based Tricks

Fast Multiplication

// x * 2   = x << 1
// x * 4   = x << 2
// x * 8   = x << 3
// x * 10  = (x << 3) + (x << 1)
// x * 12  = (x << 3) + (x << 2)
// x * 15  = (x << 4) - x
// x * 100 = (x << 6) + (x << 5) + (x << 2)

Computing Average Without Overflow

// (a + b) / 2 might overflow if a + b > INT_MAX
// Fix: use the bit manipulation identity
int average(int a, int b) {
    return (a & b) + ((a ^ b) >> 1);
}
// (a & b) = bits both have in common (sum without carry)
// (a ^ b) = bits that differ (carry bits, halved)
// Together: exact average without overflow!

Sign of an Integer

int sign(int x) {
    return (x >> 31) | (!!x);
    // Returns -1 for negative, 0 for zero, 1 for positive
}
// x >> 31: -1 if negative, 0 otherwise (arithmetic shift)
// !!x: 0 if zero, 1 if nonzero

Reversing Bits

uint32_t reverseBits(uint32_t n) {
    n = ((n & 0x55555555) << 1)  | ((n >> 1) & 0x55555555);
    n = ((n & 0x33333333) << 2)  | ((n >> 2) & 0x33333333);
    n = ((n & 0x0F0F0F0F) << 4)  | ((n >> 4) & 0x0F0F0F0F);
    n = ((n & 0x00FF00FF) << 8)  | ((n >> 8) & 0x00FF00FF);
    n = (n << 16) | (n >> 16);
    return n;
}
// Swap adjacent bits, then pairs, then nibbles, then bytes, then halves
// Same divide-and-conquer pattern as parallel popcount

Summary

CP Pitfalls & Companion Tricks

A few shift-adjacent gotchas that show up constantly in contests:

Always shift a 64-bit literal for 64-bit masks

// BUG: 1 is `int`. `1 << 40` is UB (shift >= width of int).
long long mask = 1 << 40;          // WRONG

// FIX: shift a long long. Use 1LL (or 1ULL).
long long mask = 1LL << 40;        // OK
unsigned long long all = (1ULL << 63) | ((1ULL << 63) - 1);  // all 64 bits set
Recall: x << k or x >> k is undefined when k < 0 or k >= sizeof(x)*8. So 1 << 32 on a 32-bit int and 1LL << 64 on a 64-bit long long are both UB — don't rely on them returning 0.

Portable Logical Right Shift on Signed Types

If you need a guaranteed zero-fill right shift but your value is in a signed type, cast to the matching unsigned type first. The compiler optimizes the cast away.

int      x = -1;          // 0xFFFFFFFF
int      a = x >> 1;     // implementation-defined (almost always -1)
unsigned u = (unsigned)x >> 1;  // guaranteed 0x7FFFFFFF (logical)

// Works for long long too:
long long y = -1LL;
unsigned long long v = (unsigned long long)y >> 1;  // 0x7FFFFFFFFFFFFFFF

Isolating the Lowest Set Bit: n & -n

In two's complement, -n is ~n + 1. ANDing them isolates exactly the lowest set bit of n (zero if n == 0). Foundational for Fenwick trees and many bit DPs.

int n = 0b10110100;          // 180
int lsb = n & -n;            // 0b00000100 = 4

// Iterate over set bits in O(popcount(n)):
for (int m = n; m; m &= m - 1) {
    int bit = m & -m;        // current lowest set bit
    int idx = __builtin_ctz(m);
    // ... process bit at position idx
}

Brian Kernighan's Trick: n & (n - 1)

Subtracting 1 from n flips its lowest set bit and every zero below it. ANDing with n therefore clears the lowest set bit. This gives an O(popcount) loop instead of O(width).

int popcount(unsigned n) {
    int c = 0;
    while (n) { n &= n - 1; ++c; }
    return c;
}
// Loops only as many times as there are set bits.
// Detect power of two: n && !(n & (n - 1)).

We give counting bits its own deep dive in a dedicated post; mention these here so you have them in muscle memory before getting to the practice problems below.

Practice Problems

These all hinge on shifts, masks, or the tricks above.