Table of contents
Open Table of contents
๐ Introduction
In this blog post, we explore how to count the number of set bits (1s) in an integer in C based on the method described by K&R (The C Programming Language by Brian Kernighan and Dennis Ritchie). We will compare two approaches:
- A simple method that shifts and checks each bit sequentially.
- A faster method using K&Rโs bitwise trick
x & (x - 1).
๐ถ 1. The Simple Method: Bit-by-Bit Shift
This approach is straightforward: it inspects each bit one by one starting from the least significant bit (LSB - rightmost bit) and shifts the number to the right until all bits are processed.
Algorithm Steps
- Initialize
countto0. - While the integer is not zero:
- Check if the rightmost bit is
1usingi & 01. - If set, increment
count. - Shift the integer right by 1 bit (
i >>= 1).
- Check if the rightmost bit is
- Return
count.
Implementation
int bitcount_simple(unsigned int i) {
int count = 0;
for (; i != 0; i >>= 1) {
if (i & 01) {
count++;
}
}
return count;
}
Time Complexity: Proportional to the position of the most significant (Leftmost) 1-bit (O(total bits)) in the worst case.
โก 2. The Faster Method: K&Rโs Bitwise Trick
Instead of shifting through every bit position, we can use a clever bitwise trick that clears the rightmost set bit (1) in each iteration:
x = x & (x - 1);
๐ Why Does x & (x - 1) work?
In binary twoโs complement arithmetic, subtracting 1 from x (x - 1):
- the rightmost 1 bit in x becomes 0
- the bits after the rightmost 1 bit become
1s - the bits before the rightmost 1 bit remain unchanged because the carry generated there propagates to higher bits which remain bits unchanged.
When we perform x & (x - 1):
- The unchanged higher bits remain identical.
- The rightmost
1(which became0in x - 1) ANDed with1in x becomes0. - The trailing
0s ANDed with1s remain0.
Hence, each operation x & (x - 1) clears exactly one set bitโthe lowest-order 1.
๐ Step-by-Step Example
Letโs trace x = 196:
x = 196 -> 11000100 (binary)
-1 = 255 -> 11111111 (binary) // two's complement representation of -1
-----------------------------------------
x + (-1) = 195 -> 11000011 (binary)
// rightmost 1 bit (3rd bit from the right)
// all bits after the rightmost 1 bit (2nd and 1st bits from the right) become 1s
// all bits before the rightmost 1 bit (4th, 5th, 6th, and 7th bits from the right) remain unchanged
-----------------------------------------
x = 196 -> 11000100 (binary)
x - 1 = 195 -> 11000011 (binary) // as explained above
--------------------------------------------
x & (x-1) = 192 -> 11000000 (rightmost 1-bit - 3rd bit from the right cleared!)
--------------------------------------------
๐ป Implementation
By repeating this trick until x becomes 0, the loop executes only as many times as there are 1s:
int bitcount_faster(unsigned int x) {
int count = 0;
while(x != 0){
x = x & (x - 1);
count++;
}
return count;
}
โ๏ธ Performance Comparison
| Metric | Simple Shift Method | Kernighanโs Method |
|---|---|---|
| Iterations | Equal to total bits up to the Most Significant Bit (MSB) | Equal only to the number of set bits (1s) |
| Best Case | O(1) when x == 0 | O(1) when x == 0 |
| Worst Case | O(total bits up to the (MSB)) (e.g., 32 or 64 steps) | O(number of 1s) |
๐ Conclusion
K&Rโs algorithm x & (x - 1) is an elegant bitwise technique that skips zero-bits entirely. It reduces execution time, making it the preferred method for counting set bits.