Bit Manipulation: Logic, Complements, and High-Performance Tricks
hardBit ManipulationTwo's ComplementSign BitsBitwise Tricks
Binary Representation: Two's Complement
In Java (and most modern languages), signed integers are represented using 32-bit Two's Complement:
| Value | Binary (32-bit) | Rationale |
|---|---|---|
| 0 | 0000...0000 |
Zero state |
| 1 | 0000...0001 |
LSB set |
| -1 | 1111...1111 |
$~0 + 1$ |
| MAX_INT ($2^{31}-1$) | 0111...1111 |
Highest positive value |
| MIN_INT ($-2^{31}$) | 1000...0000 |
Sign bit set, no others |
Key Identities:
- Negative representation:
-n = ~n + 1. - Lowbit:
n & (-n)=n & (~n + 1). This isolates the lowest1bit.
The Sign Bit and Arithmetic Shifts
int n = -8; // Binary: 1111...1000
int a = n >> 1; // Arithmetic shift: 1111...1100 = -4 (Preserves sign)
int b = n >>> 1; // Logical shift: 0111...1100 = LARGE POSITIVE (Pads with 0)
Real-world Application: Safe binary search midpoint.
// Prevents overflow in int calculations (left + right)
int mid1 = (left + right) >>> 1; // Correct even if (left + right) exceeds MAX_INT
No-Branch Selection (Bitwise instead of if)
In performance-sensitive code (SIMD/Game Engines), you can replace branching with bit-logic to avoid pipeline stalls.
// Select a or b based on condition 'cond' (0 or 1)
// If cond=1, mask=-1 (all 1s). If cond=0, mask=0.
int select(int cond, int a, int b) {
int mask = -cond;
return (a & mask) | (b & ~mask);
}
// Absolute value without Math.abs or if
int abs(int n) {
int mask = n >> 31; // 0 for positives, -1 for negatives
return (n + mask) ^ mask; // Inverts negatives and adds 1
}
Lowbit and Binary Indexed Trees (Fenwick Trees)
The lowbit operation is the backbone of the BIT (Binary Indexed Tree), allowing $O(\log N)$ prefix sum queries and point updates.
public int lowbit(int x) {
return x & (-x);
}
// update(i, delta): i += lowbit(i) moves up the tree
// query(i): i -= lowbit(i) moves down the tree to aggregate sums
Common Bitwise Pitfalls
1. Operator Precedence
The & | ^ operators have lower priority than equality == and relational operators. Always use parentheses.
if ((n & 1) == 0) { ... } // Correct
if (n & 1 == 0) { ... } // BUG: equivalent to n & (1 == 0)
2. Shift Overflow
In Java, shifting an int by 32 results in no shift at all because the shift amount is computed modulo 32.
int x = 1 << 32; // Equivalent to 1 << 0 = 1
3. Signed Bytes
A signed byte 0xFF in Java is -1. To get the unsigned value $255$:
int value = b & 0xFF; // Promotes to int then masks to lower 8 bits
Engineering Proficiency Checklist
| Problem | Core Concept | difficulty |
|---|---|---|
| Power of Two | n & (n - 1) == 0 |
Easy |
| Total Hamming | Count ones per bit position |
Medium |
| Range AND | Highest common prefix | Medium |
| Max XOR | Greedy Prefix Trie / Hashing | Medium |
| Gray Code | i ^ (i >> 1) |
Medium |
| Team Coverage | State Compression DP | Hard |
Conclusion: The First Principles of Bits
- Two's Complement unifies addition across positive and negative domains.
- XOR is addition without carrying; AND identifies carries.
- n & (n-1) is the standard tool for counting bits $O(K)$.
- n & (-n) is the entry point for BIT/Fenwick trees.
- Masks turn an integer into an efficient, indexed boolean set.
- State Compression is the bridge between combinatorial problems and efficient DP. 筋