Counting the number of ones in a binary number
Note: This article assumes you're familiar with Binary numbers and operations that can be done with them.
Note: In this article, we will only be dealing with non-negative integers.
In this article I will show three different ways of getting the number of ones, or set bits of a binary number. To start off, you can see the expected values here:
This is also called the Hamming Weight of a number. This has uses in some algorithms, such as distributed hash tables, chess algorithms, some data structures, and even one algorithm for finding 5 wordle words which use 25 unique letters efficiently!
Iteration
The simplest, naive method and the one that probably comes to mind first would be to iterate over every single bit of the binary number, and check if it is a one, adding to the total if it is. For a number n his takes around log2(n) steps.
Implemented in JavaScript, this algorithm would look like this:
(note: this and all other code snippets use
BigInts
in order to remain precise even with large numbers. As a result, all functions
take and return BigInts
)
Each iteration it adds the last bit of the binary number to the total, and then shifts the entire number down. This way, we can avoid having to calculate the length of the number in bits.
Decreasing + Iteration
Believe it or not, we can do this in (usually) fewer than log2n steps! This slightly trickier way of counting comes from an observation that subtracting one from a number flips all the bits coming before the first set bit, as well as the set bit itself.
Combining this with the AND bitwise operation, we can see that
(n) & (n-1)
will set the last set bit to zero, as taking the AND of a
flipped bit and the bit itself will always result in 0. This means that after doing
this operation, n will have one fewer set bits. Combining this all together, we can
repeatedly take the AND of n and n-1 and set it to n, and simply count how many
iterations it takes for the process to result in 0. That gives us the answer we are
looking for!
Implemented, this would look like
Constant time
There is a constant time solution for this. In our case, we will just show it for numbers up to 232, but it can be easily expanded with to a higher range.
First, lets just start with just a 3 bit number: n = a2 * 22 + a1 * 21 + a0 * 20, where an is the nth digit of our number. Our task here is to just isolate the coefficients a2 + a1 + a0. If we write out the results of shifting down:
n >> 0 | a2 * 22 + a1 * 21 + a0 * 20 |
n >> 1 | a2 * 21 + a1 * 20 + a0 * 0 |
n >> 2 | a2 * 20 + a1 * 0 + a0 * 0 |
From this, we can see that n - (n>>1) - (n>>2) is a2 + a1 + a0: because for every single digit an * 2n, an * 2n-1 , an * 2n - 2 ... an * 20 is subtracted. Taking an out of the parentheses we get an(2n - 2n-1 - ... - 20) which results in just an. This works just as well for 32 bit numbers!
Sidenote: why is this the case? Can we prove that 2n - (2n-1 + 2n-2 + ... + 20) = 1? Well, this can be shown in a clever way! If we look the the binary of the sum of the first n-1 powers of two, each power of two adds one "1" to the binary number, thus the sum is 111...1112 with n-1 ones. If we add one to this number, we of course get 100..0002, where the first set bit is the the nth position, which is equal to 2n! Therefore, 2n is one more than the sum of the first n-1 powers of two.
In code, this would look like:
This approach can be modified slightly so that the result can be calculated without any loops or conditionals!
With just 1MB and minification, you can extend this function to work with numbers below 284257, or number with around 25000 decimal digits. However, using a loop would probably be better!
Conclusion
Thanks for reading! You can find the source code and tests at github.com/Bulmenisaurus/SoME2.