0x55555555, 0x33333333, 0x0f0f0f0f, etc. are used to mask out bits in specific locations. These bit masks can be generated by dividing the maximum integer 0xffffffff (or simply ~0u) against Fermat numbers 3, 5, 17, 257, 65537, etc.| Hex | Binary | Expression |
0x5555... | 0101010101010101... | ~0u / 3 |
0x3333... | 0011001100110011... | ~0u / 5 |
0x0f0f... | 0000111100001111... | ~0u / 17 |
0x00ff... | 0000000011111111... | ~0u / 257 |
| and so on... | ||
It's not a coincidence that integers with all one bits set can be divided cleanly by a Fermat number, and this works with 8-bit, 16-bit, 32-bit, 64-bit, and beyond. To understand why, a \(k\)-bit all-one integer is \(2^k - 1\). In particular, \(k = 2^n\) in our case. We can factor \(2^{2^n} - 1\) using the formula \(a^2 - b^2 = (a + b)(a - b)\) we learned in grade school, here using the special case \(b = 1\), so \(a^2 - 1 = (a + 1)(a - 1)\).
\begin{align}
2^{2^n} - 1 & = (2^{2^{n - 1}} + 1)(2^{2^{n - 1}} - 1) \\
& = (2^{2^{n - 1}} + 1)(2^{2^{n - 2}} + 1)(2^{2^{n - 2}} - 1) \\
& = (2^{2^{n - 1}} + 1)(2^{2^{n - 2}} + 1)(2^{2^{n - 3}} + 1)(2^{2^{n - 3}} - 1) \\
& = \dots
\end{align}
Conveniently, the last term \(2^{2^0} - 1\) is just \(1\), so we can write the product in terms of \(F_i = 2^{2^i} + 1\) for the \(i\)-th Fermat number,
\[ 2^{2^n} - 1 = \prod_{i=0\dots n-1}{2^{2^i} + 1} = \prod_{i=0\dots n-1}{F_i} \]
For example,
| \(k\) | \(2^k - 1\) | Expression |
| 2 | 3 | \(3 \) |
| 4 | 15 | \(3 \times 5 \) |
| 8 | 255 | \(3 \times 5 \times 17 \) |
| 16 | 65535 | \(3 \times 5 \times 17 \times 257\) |
| 32 | 4294967295 | \(3 \times 5 \times 17 \times 257 \times 65537\) |
| and so on... | ||
So how are Fermat numbers related to bit mask construction? It turns out that if you write 3, 5, 17, 257, 65537 in binary, you get
11, 101, 1 0001, 1 0000 0001, and 1 0000 0000 0000 0000 0000 0000 0000 0001 respectively. If you multiply 101 (5) and 1 0001 (17), you get alternating patterns of 0101 0101 (85). If you multiply this by 11 (3), you get an 8-bit all-one integer 1111 1111 (255). Conversely, you can take a pattern "out of circulation" by dividing the all-one integer by a Fermat number which is one of its factors.
Appendix: Counting the number of one bits using C++ template meta-programming
We start with the straightforward bit vector algorithm:x = ((x >> 1) & 0x55555555) + (x & 0x55555555); x = ((x >> 2) & 0x33333333) + (x & 0x33333333); x = ((x >> 4) & 0x0f0f0f0f) + (x & 0x0f0f0f0f); // etc.which can be represented by the following for-loop:
for (int n = 1; n < sizeof(Tp) * 8; n *= 2) Tp mask = ~0 / ((1 << n) + 1)); x = ((x >> n) & mask) + (x & mask); }In the template meta-programming version, the template
__unroll_shift_mask_add essentially unrolls into the for-loop above, but in a manner that is agnostic to the actual integer type.
template<unsigned int n, typename Tp>
struct __unroll_shift_mask_add {
static Tp compute(Tp x) {
Tp y = __unroll_shift_mask_add<n / 2, Tp>::compute(x);
Tp mask = ~Tp(0) / ((Tp(1) << n) + 1);
return ((y >> n) & mask) + (y & mask);
}
};
template<typename Tp>
struct __unroll_shift_mask_add<0u, Tp> {
static Tp compute(Tp x) { return x; }
};
template<typename Tp>
Tp one_bits(Tp x) {
return __unroll_shift_mask_add<sizeof(Tp) * 8 / 2, Tp>::compute(x);
}
No comments:
Post a Comment