Skip to main content

Counting Bits

Introduction

When working with binary representations in programming, one common task is counting the number of set bits (1's) in an integer. This operation, while seemingly simple, is fundamental to many algorithms and has several interesting implementation techniques.

In this tutorial, we'll explore different methods to count bits, understand the underlying principles, and see where this knowledge can be applied in real-world scenarios.

Understanding Binary Representation

Before we dive into counting bits, let's briefly review how integers are represented in binary.

In binary, each digit (or bit) can be either 0 or 1. The value of a number is determined by the position of each bit, where each position represents a power of 2:

Decimal 13 in binary is 1101:
1 1 0 1
2^3 2^2 2^1 2^0
8 + 4 + 0 + 1 = 13

Counting bits means determining how many 1's appear in this binary representation.

Basic Approach: Iterative Shifting

The simplest way to count bits is to check each bit one by one. We can do this by using right shift operations and checking the least significant bit each time:

java
public int countBits(int n) {
int count = 0;
while (n != 0) {
count += n & 1; // Check the least significant bit
n >>>= 1; // Unsigned right shift by 1
}
return count;
}

Let's trace through this with the number 13 (1101 in binary):

  1. n = 1101, check least significant bit: 1 & 1 = 1, count = 1, n = 110
  2. n = 110, check least significant bit: 0 & 1 = 0, count = 1, n = 11
  3. n = 11, check least significant bit: 1 & 1 = 1, count = 2, n = 1
  4. n = 1, check least significant bit: 1 & 1 = 1, count = 3, n = 0
  5. n = 0, exit the loop

Result: 13 (1101) has 3 bits set to 1.

The Brian Kernighan's Algorithm

There's a clever optimization known as Brian Kernighan's algorithm. It uses the fact that the expression n & (n-1) clears the rightmost set bit in n:

java
public int countBitsKernighan(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1); // Clear the least significant set bit
count++;
}
return count;
}

Let's trace through this with the number 13 (1101 in binary):

  1. n = 1101, n-1 = 1100, n & (n-1) = 1100, count = 1, n = 1100
  2. n = 1100, n-1 = 1011, n & (n-1) = 1000, count = 2, n = 1000
  3. n = 1000, n-1 = 0111, n & (n-1) = 0000, count = 3, n = 0000
  4. n = 0000, exit the loop

Result: 13 (1101) has 3 bits set to 1.

This algorithm is more efficient because it only iterates as many times as there are set bits, rather than iterating through all bits.

Lookup Table Method

For small integers, we can use a lookup table for even faster bit counting:

java
public class BitCounter {
private static final byte[] BIT_COUNT_TABLE = new byte[256];

static {
// Initialize the lookup table
for (int i = 0; i < 256; i++) {
BIT_COUNT_TABLE[i] = (byte) Integer.bitCount(i);
}
}

public int countBits(int n) {
// Count bits in each byte and sum
return BIT_COUNT_TABLE[n & 0xff] +
BIT_COUNT_TABLE[(n >>> 8) & 0xff] +
BIT_COUNT_TABLE[(n >>> 16) & 0xff] +
BIT_COUNT_TABLE[(n >>> 24) & 0xff];
}
}

This breaks the 32-bit integer into 4 bytes and looks up the bit count for each byte in a pre-computed table.

Built-in Methods

Most programming languages provide built-in functions for bit counting:

java
// Java
int bitCount = Integer.bitCount(n);
python
# Python
bit_count = bin(n).count('1') # Simple but less efficient
# or
bit_count = n.bit_count() # Python 3.10+ only
javascript
// JavaScript
const bitCount = n.toString(2).split('0').join('').length;
cpp
// C++
int bitCount = __builtin_popcount(n); // GCC intrinsic function

Dynamic Programming Approach

When we need to count bits for a range of numbers (like 0 to n), a dynamic programming approach can be efficient:

java
public int[] countBitsForRange(int n) {
int[] result = new int[n + 1];
for (int i = 1; i <= n; i++) {
// The number of bits in i is equal to the number of bits in i/2
// plus 1 if i is odd (i.e., if the least significant bit is 1)
result[i] = result[i >> 1] + (i & 1);
}
return result;
}

This works because for any number i, the number of bits in i is:

  • The number of bits in i/2 (since dividing by 2 is equivalent to right-shifting)
  • Plus 1 if i is odd (which means the least significant bit is 1)

Real-World Applications

Counting bits might seem like a theoretical exercise, but it has several practical applications:

  1. Error detection & correction: Hamming distance calculations in network protocols
  2. Cryptography: In various cryptographic algorithms and hashing functions
  3. Data compression: In algorithms that rely on bit-level operations
  4. Low-level systems programming: In device drivers and embedded systems
  5. Performance optimization: In cases where counting bits is more efficient than other operations

Example: Calculating Hamming Distance

The Hamming distance between two integers is the number of positions at which the corresponding bits differ. It's calculated by XORing the numbers and counting the bits in the result:

java
public int hammingDistance(int x, int y) {
return Integer.bitCount(x ^ y);
}

Example: Checking Parity

Parity checking (determining if the number of set bits is odd or even) is used in error detection:

java
public boolean isEvenParity(int n) {
return Integer.bitCount(n) % 2 == 0;
}

Optimizations for Specific Cases

In some cases, specialized techniques can be even faster:

Parallel Bit Counting

For 32-bit integers:

java
public int countBitsParallel(int n) {
n = n - ((n >>> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
n = (n + (n >>> 4)) & 0x0F0F0F0F;
n = n + (n >>> 8);
n = n + (n >>> 16);
return n & 0x3F;
}

This algorithm works by:

  1. Counting bits in pairs
  2. Then summing those counts in groups of 4
  3. Then in groups of 8, 16, and 32

While complex, this parallel approach is very efficient for hardware implementation.

Summary

Counting bits in binary representations is a fundamental operation with multiple implementation strategies:

  1. Iterative Shifting: Check each bit one by one (O(log n) time)
  2. Brian Kernighan's Algorithm: Clear the rightmost set bit in each iteration (O(s) time where s is the number of set bits)
  3. Lookup Table: Pre-compute bit counts for small values
  4. Built-in Methods: Use language-provided functions
  5. Dynamic Programming: Efficient for counting bits in ranges
  6. Parallel Bit Counting: Optimized for hardware implementation

The choice of method depends on your specific needs: Are you counting bits frequently? Do you need to count bits in a range of numbers? Is performance critical?

Understanding bit counting not only helps with algorithm optimization but also provides insights into how computers work at the fundamental level.

Exercises

  1. Implement the different bit counting methods and compare their performance for various inputs.
  2. Write a function to determine if a number is a power of 2 using bit counting.
  3. Implement a function countBitsInRange(int start, int end) that returns an array where each entry contains the bit count for the corresponding number in the range.
  4. Create a function to find the number with the maximum bit count in a given array.
  5. Implement a parity bit generator for error detection in data transmission.

Additional Resources

Happy bit counting!



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)