110
11100
111110000
1111111000000
1111111111111000000000000However, the reason I said AVX is because it can be used for checking equality of 5 (well, 8) numbers simultaneously. This solution requires AVX2 and is probably faster than the optimized reference code.
// Copy x to all 8 elements.
__m256i a = _mm256_set1_epi32(x);
// Duplicate 6 because we need to fill all 8 elements.
__m256i b = _mm256_set_epi32(6, 28, 496, 8128, 33550336, 6, 6, 6);
// Check for element-wise equality.
__m256i c = _mm256_cmpeq_epi32(a, b);
// Check if any bytes are nonzero.
return _mm256_movemask_epi8(c) != 0;
https://godbolt.org/z/6W8rZyInterestingly the compiler doesn't do this optimization, even though it could. So maybe I'm wrong. No real way to know without benchmarking, though.
2^(p - 1)(2^p - 1)
for some prime `p`. In code that's ((1 << p) - 1) << (p - 1)
for `p` = 2, 3, 5, 7 and 13, which is why you're seeing that interesting pattern of bits.(There are no odd perfect numbers in the range under consideration.)
edit: Not to mention that all 5 numbers are even, so a parity check could also help for odd number inputs (but it makes it slower for even numbers). I do not think that AVX has anything to offer here though.
Edit: And yeah, you need a fast popcnt here.
return (x & 0xFE0000001 == 0) &&
(x == 6 || x == 28 || x == 496 || x == 8128 || x == 33550336):
The original code does about 5 comparisons in each call. The improvement always does a binary ‘and’ and 1 comparison, and only does the original 5 in 1/256 of the calls. bool isPerfect(uint64_t x) {
int ctz = __builtin_ctzll(x);
return ((0x40051056 >> ctz) & 1) && ((x >> ctz) + 1) == (uint64_t(1) << (ctz + 1));
}But this is still dumb! lol.
(Yes I realize it would just be an iota function in c++)