Advanced bit arithmetic
Beyond the everyday tricks, a few classic bit algorithms keep appearing: parity, Gray code, bit reversal, addition without +, exponentiation by squaring, and tries over bits. Each shows a different way to think about a number as a row of bits.
Learning goals
- Compute parity by folding with XOR
- Generate and invert Gray codes
- Reverse the bits of an int with masks and shifts
- Add two ints with only bit operations and detect overflow
- Raise a number to a power in O(log n) multiplications
- Use a trie over bits to find a maximum XOR
Prerequisites
Parity
The parity of a number is whether it has an odd number of 1 bits. XOR is addition modulo 2, so XOR-ing all the bits together gives the parity. Instead of looping over 32 bits, fold the number onto itself: XOR the upper half into the lower half, then the upper half of that, and so on until one bit remains. Step through 182 in the animation: three folds are enough for 8 bits (five for 32).
Parity is used for error detection: a parity bit makes the total number of ones even or odd, so any single flipped bit is detected.
1static int parity(int x) { // 32-bit version2x ^= x >>> 16;3x ^= x >>> 8;4x ^= x >>> 4;5x ^= x >>> 2;6x ^= x >>> 1;7return x & 1;8}9// equivalent: Integer.bitCount(x) & 1
Worst
O(log w) for w-bit words
Space
O(1)
Parity by folding with XOR
Interactive · edit the inputx = 182
Parity is whether the count of 1 bits is odd. 182 has 5 set bits. XOR is addition modulo 2, so folding the number onto itself keeps the parity.
1static int parity(int x) { // 8-bit value2x ^= x >> 4; // fold the upper half onto the lower3x ^= x >> 2;4x ^= x >> 1;5return x & 1; // 1 if the number of set bits is odd6}
Live state
x
182
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | x=182 | Parity is whether the count of 1 bits is odd. 182 has 5 set bits. XOR is addition modulo 2, so folding the number onto itself keeps the parity. |
| 2 | L2 | shift=4 x=189 | XOR with x >> 4: each bit now holds the parity of 8 original bits. |
| 3 | L3 | shift=2 x=146 | XOR with x >> 2: each bit now holds the parity of 4 original bits. |
| 4 | L4 | shift=1 x=219 | XOR with x >> 1: each bit now holds the parity of 2 original bits. |
| 5 | L5 | return=1 | Bit 0 now holds the parity of all 8 bits: 1 (odd number of 1s). Three steps instead of eight. |
Gray code
A Gray code lists all n-bit numbers in an order where consecutive values differ in exactly one bit. The binary reflected Gray code of i is i ^ (i >> 1). Run the animation with 3 bits: every new code differs from the previous in a single highlighted bit, and the last code differs from the first in one bit too.
Gray codes are used in rotary encoders (no glitch when a position changes), Karnaugh maps, and puzzle solving. To convert back, XOR together the code shifted right by 1, 2, 3, … until it is zero.
1static int toGray(int i) { return i ^ (i >> 1); }23static int fromGray(int g) {4int n = g;5while ((g >>= 1) != 0) n ^= g; // n = g ^ (g >> 1) ^ (g >> 2) ^ ...6return n;7}
Gray code: one bit changes at a time
Interactive · edit the inputGray codes
A Gray code lists all 8 3-bit numbers so that neighbours differ in exactly one bit. The formula is i ^ (i >> 1).
1static List<Integer> grayCode(int n) {2List<Integer> result = new ArrayList<>();3for (int i = 0; i < (1 << n); i++) {4result.add(i ^ (i >> 1)); // binary XOR itself shifted right5}6return result;7}
Live state
n
3
count
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=3 count=8 | A Gray code lists all 8 3-bit numbers so that neighbours differ in exactly one bit. The formula is i ^ (i >> 1). |
| 2 | L4 | i=0 gray=000 | i = 0 gives 0. |
| 3 | L4 | i=1 gray=001 | Compared with the previous code 000, only bit 0 changed. |
| 4 | L4 | i=2 gray=011 | Compared with the previous code 001, only bit 1 changed. |
| 5 | L4 | i=3 gray=010 | Compared with the previous code 011, only bit 0 changed. |
| 6 | L4 | i=4 gray=110 | Compared with the previous code 010, only bit 2 changed. |
| 7 | L4 | i=5 gray=111 | Compared with the previous code 110, only bit 0 changed. |
| 8 | L4 | i=6 gray=101 | Compared with the previous code 111, only bit 1 changed. |
| 9 | L4 | i=7 gray=100 | Compared with the previous code 101, only bit 0 changed. |
| 10 | L6 | count=8 | Each step flips exactly one bit, so a rotary encoder or a Karnaugh map never sees two bits change at once. The sequence is also cyclic: the last code differs from the first in one bit. |
Bit reversal
To reverse the bits of a 32-bit int, swap adjacent bits, then adjacent pairs, then nibbles, then bytes, then halves. Each step uses a mask and a shift, and the whole thing takes 5 steps instead of 32. Integer.reverse does exactly this.
Bit reversal is the permutation at the start of the fast Fourier transform and appears in problems that ask to reverse the binary form of a number.
1static int reverseBits(int x) {2x = ((x & 0x55555555) << 1) | ((x >>> 1) & 0x55555555); // swap adjacent bits3x = ((x & 0x33333333) << 2) | ((x >>> 2) & 0x33333333); // swap adjacent pairs4x = ((x & 0x0F0F0F0F) << 4) | ((x >>> 4) & 0x0F0F0F0F); // swap nibbles5x = ((x & 0x00FF00FF) << 8) | ((x >>> 8) & 0x00FF00FF); // swap bytes6return (x << 16) | (x >>> 16); // swap halves7}8// same as Integer.reverse(x)
Carry and overflow
Addition can be built from bit operations. The XOR of two numbers is their sum without carries, and (a & b) << 1 is the carries. Add them again, and repeat until no carry remains. This works for negative numbers too, because of two's complement.
To detect int overflow of a + b without using long, check whether both operands have the same sign and the result has a different one: ((a ^ r) & (b ^ r)) < 0 where r = a + b. Java also provides Math.addExact, which throws an ArithmeticException.
1static int add(int a, int b) {2while (b != 0) {3int carry = (a & b) << 1; // positions where both bits are 1 carry to the next one4a ^= b; // sum without carries5b = carry;6}7return a;8}910static boolean addOverflows(int a, int b) {11int r = a + b;12return ((a ^ r) & (b ^ r)) < 0; // operands share a sign that the result lost13}
Binary exponentiation
To compute a^b, look at the binary form of b. Each set bit contributes one of the powers a, a², a⁴, a⁸, …. Square the base every step, multiply it into the result when the current bit of b is 1, and shift b right. The loop runs once per bit of b, so it needs O(log b) multiplications instead of b - 1.
In the animation, 3^13 with 13 = 1101₂ multiplies in 3, 3⁴ and 3⁸: 3 × 81 × 6561 = 1,594,323. With a modulus applied at every multiplication this is how modular powers are computed in cryptography and combinatorics.
1static long power(long a, int b) {2long result = 1;3while (b > 0) {4if ((b & 1) == 1) result *= a; // this bit of b is set5a *= a; // a, a^2, a^4, a^8, ...6b >>= 1;7}8return result;9}1011static long powMod(long a, long b, long m) {12long result = 1;13a %= m;14for (; b > 0; b >>= 1, a = a * a % m) {15if ((b & 1) == 1) result = result * a % m;16}17return result; // m < 2^31 keeps a * a inside a long18}
Worst
O(log b)
Space
O(1)
Binary exponentiation
Interactive · edit the inputb = 13
a
result
3^13: write 13 in binary (1101). Each set bit contributes one power 3^1, 3^2, 3^4, 3^8, and the others are skipped.
1static long power(long a, int b) {2long result = 1;3while (b > 0) {4if ((b & 1) == 1) result *= a; // this bit of b is set5a *= a; // a, a^2, a^4, a^8, ...6b >>= 1; // move to the next bit7}8return result;9}
Live state
a
3
b
13
result
1
Execution trace (dry run table)Show 17 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | a=3 b=13 result=1 | 3^13: write 13 in binary (1101). Each set bit contributes one power 3^1, 3^2, 3^4, 3^8, and the others are skipped. |
| 2 | L4 | a=3 b=13 result=1 | The lowest bit of b is 1, so multiply the current power of a into the result. |
| 3 | L4 | a=3 b=13 result=3 | result = 3. |
| 4 | L5 | a=9 b=13 result=3 | Square the base: a = 9. It is now the power for the next bit. |
| 5 | L6 | a=9 b=6 result=3 | b >>= 1 drops the bit we just used. b = 6. |
| 6 | L4 | a=9 b=6 result=3 | The lowest bit of b is 0, so skip this power. |
| 7 | L5 | a=81 b=6 result=3 | Square the base: a = 81. It is now the power for the next bit. |
| 8 | L6 | a=81 b=3 result=3 | b >>= 1 drops the bit we just used. b = 3. |
| 9 | L4 | a=81 b=3 result=3 | The lowest bit of b is 1, so multiply the current power of a into the result. |
| 10 | L4 | a=81 b=3 result=243 | result = 243. |
| 11 | L5 | a=6561 b=3 result=243 | Square the base: a = 6561. It is now the power for the next bit. |
| 12 | L6 | a=6561 b=1 result=243 | b >>= 1 drops the bit we just used. b = 1. |
| 13 | L4 | a=6561 b=1 result=243 | The lowest bit of b is 1, so multiply the current power of a into the result. |
| 14 | L4 | a=6561 b=1 result=1594323 | result = 1594323. |
| 15 | L5 | a=43046721 b=1 result=1594323 | Square the base: a = 43046721. It is now the power for the next bit. |
| 16 | L6 | a=43046721 b=0 result=1594323 | b >>= 1 drops the bit we just used. b = 0. |
| 17 | L8 | return=1594323 | 3^13 = 1594323 using 7 multiplications instead of 12. The loop runs once per bit of b, so O(log b). |
Bitwise trie introduction
A bitwise trie stores numbers as paths of bits from the most significant to the least. Each node has two children, for 0 and 1. It answers questions about XOR: to find the stored number that gives the largest XOR with x, walk down and at each bit prefer the child with the opposite bit.
The maximum XOR of any two numbers in an array of n values takes O(n · 32) with this trie, much better than the O(n²) of trying all pairs. Tries are covered fully in Chapter 28.
1static int maxXorPair(int[] nums) {2int[][] child = new int[nums.length * 31 + 2][2]; // child[node][bit], 0 means none3int nodes = 1;4int best = 0;5for (int x : nums) {6int cur = 0, xorHere = 0, probe = 0;7for (int bit = 30; bit >= 0; bit--) {8int b = (x >> bit) & 1;9// insert x10if (child[cur][b] == 0) child[cur][b] = nodes++;11cur = child[cur][b];12}13probe = 0;14for (int bit = 30; bit >= 0; bit--) {15int b = (x >> bit) & 1;16if (child[probe][1 - b] != 0) { xorHere |= 1 << bit; probe = child[probe][1 - b]; }17else probe = child[probe][b];18}19best = Math.max(best, xorHere);20}21return best;22}
Practice ladder
Now try it yourself
Easy
- Parity of a 32-bit integer
- Convert a Gray code back to binary
- Reverse the bits of an 8-bit value
Medium
- Add two integers without + or -
- Pow(x, n) in O(log n) including negative exponents
- Generate the n-bit Gray code sequence
Advanced
- Maximum XOR of two numbers in an array
- Count pairs with XOR less than k using a bitwise trie
- Modular inverse by exponentiation (Fermat) for a prime modulus
Key takeaways
- Parity folds with XOR in log w steps; Gray code is i ^ (i >> 1).
- Bit reversal and addition are built from masks, shifts, XOR and carries.
- Exponentiation by squaring reads the bits of the exponent: O(log b); a trie over bits answers maximum-XOR queries.