In low-level systems programming and coding interviews, people always use the expression n & (n - 1) to count set bits (Hamming Weight) or check if a number is a power of 2.
I know it works, but what is the exact binary arithmetic proof behind why subtracting 1 from an integer flips all bits up to and including the lowest set bit?
The trick
n & (n - 1)is one of the most elegant one-liners in computer engineering. Let’s look at the exact bitwise mechanics so the mathematical proof becomes obvious.1. What happens when you subtract 1 in binary?
Think about standard base-10 math: when you subtract 1 from
1000, what happens? The lowest non-zero digit (1) becomes 0, and all trailing zeroes become 9s:0999.Binary works exactly the same way, but with 0s and 1s:
Any positive binary integer can be written in this general form:
where the
1shown is the lowest set bit (the rightmost 1), followed by zero or more0s.When you compute
n - 1:1remains completely untouched.1turns into a0(borrowing from the subtraction).0s flip into1s!2. The Bitwise AND Operation: n & (n – 1)
Now perform a bitwise AND between
nandn - 1:Look at what happened:
prefixmatched identically → remains preserved.1was paired with0→ becomes0!0s were paired with1s → remain0!Conclusion: The operation
n & (n - 1)turns off the lowest set bit innand leaves every other bit completely unchanged. Pure mathematical magic!3. Real-World Applications
A. Counting Set Bits in O(k) time (where k is number of 1s)
Instead of looping 32 or 64 times, Brian Kernighan’s algorithm loops only as many times as there are 1-bits:
If a 64-bit integer has only two set bits, this loop executes exactly twice and terminates!
B. Instant Power of Two Check in O(1)
A power of two in binary has exactly one set bit (e.g.
8 = 1000_2,16 = 10000_2). If you strip that single bit and the result is 0, it was a power of 2:C. Hardware POPCNT Alternative
On modern x86_64 CPUs, you have the dedicated hardware assembly instruction
POPCNT(or__builtin_popcountin GCC/Clang), which computes set bits in a single CPU cycle. But when writing portable code or kernel routines without AVX/SSE guarantees, Brian Kernighan’s algorithm remains the golden standard.Here is the modern C++20 bit-manipulation implementation. In modern C++ (C++20), you also have
std::popcountfrom the<bit>header which compiles to the hardwarePOPCNTinstruction.Efficiency: Runs only as many iterations as there are
1bits in the integer.