Finding the First Set Bit Using Bitwise Algorithms
Finding the index of the first set bit—the position of the least
significant 1 in a binary representation—is a fundamental
operation in computer science with applications in graphics, chess
engines, and data compression. Bitwise algorithms solve this problem in
constant or near-constant time by first isolating the lowest set bit
using two’s complement arithmetic, then determining its positional index
using hardware instructions, De Bruijn sequences, or binary search.
Isolating the Lowest Set Bit
The critical first step in most bitwise approaches is isolating the least significant bit. This is achieved using the formula:
\[\text{isolated\_bit} = n \ \& \ (-n)\]
In modern computing, negative integers are represented using two’s
complement notation, where \(-n\) is
equivalent to \((\sim n) + 1\).
Inverting the bits (\(\sim n\)) flips
all bits, and adding \(1\) ripples a
carry through the inverted trailing zeros until it reaches the first
zero, flipping it back to \(1\). When
performing a bitwise AND between the original integer \(n\) and \(-n\), all higher bits cancel out, leaving
only the lowest set bit as a pure power of two (e.g.,
0000 1000).
Method 1: Hardware Instructions (CTZ and BSF)
Modern processors include dedicated instruction set architecture (ISA) operations that determine the index in a single CPU cycle.
- x86/x64: Uses
TZCNT(Count Trailing Zeros) orBSF(Bit Scan Forward). - ARM: Uses
CLZ(Count Leading Zeros) combined with bit reversal or arithmetic subtraction.
Most programming languages expose these instructions through compiler
intrinsics, such as __builtin_ctz() in GCC/Clang or
_BitScanForward() in MSVC. The CPU executes this using
dedicated barrel shifters and logic gates, finding the index in \(O(1)\) time with zero branching.
Method 2: De Bruijn Sequences
When dedicated hardware instructions are unavailable, De Bruijn sequences offer an \(O(1)\) lookup solution without conditional branching. A De Bruijn sequence contains every sequence of length \(k\) over an alphabet.
Once the lowest bit is isolated into a power of two using
n & -n, multiplying it by a specially chosen 32-bit De
Bruijn constant shifts the unique sequence into the most significant
bits. Shifting the result down produces a unique key between \(0\) and \(31\), which directly indexes a precomputed
32-element array containing the bit positions.
Method 3: Binary Search (Divide and Conquer)
For environments without lookup tables or hardware intrinsics, a binary search approach determines the index in \(O(\log B)\) steps, where \(B\) is the bit width of the integer (requiring only 5 checks for a 32-bit integer).
The algorithm checks whether the set bit resides in the lower or upper half of the register using bitmasks:
- Check if the lowest set bit is in the lower 16 bits
(
n & 0x0000FFFF). If not, add 16 to the index counter and shift the number right by 16 bits. - Check the lower 8 bits (
n & 0x00FF). If not present, add 8 to the counter and shift right by 8. - Repeat for 4 bits, 2 bits, and 1 bit.
By eliminating half of the remaining bit positions at each step, the exact zero-based index is determined rapidly without sequential loops.