I need to test whether the positions (from 0 to 31 for a 32bit integer) with bit value 1 form a contiguous region. For example:
00111111000000000000000000000000 is contiguous
00111111000000000000000011000000 is not contiguous
I want this test, i.e. some function has_contiguous_one_bits(int)
, to be portable.
One obvious way is to loop over positions to find the first set bit, then the first non-set bit and check for any more set bits.
I wonder whether there exists a faster way? If there are fast methods to find the highest and lowest set bits (but from this question it appears there aren't any portable ones), then a possible implementation is
bool has_contiguous_one_bits(int val)
{
auto h = highest_set_bit(val);
auto l = lowest_set_bit(val);
return val == (((1 << (h-l+1))-1)<<l);
}
Just for fun, here are the first 100 integers with contiguous bits:
0 1 2 3 4 6 7 8 12 14 15 16 24 28 30 31 32 48 56 60 62 63 64 96 112 120 124 126 127 128 192 224 240 248 252 254 255 256 384 448 480 496 504 508 510 511 512 768 896 960 992 1008 1016 1020 1022 1023 1024 1536 1792 1920 1984 2016 2032 2040 2044 2046 2047 2048 3072 3584 3840 3968 4032 4064 4080 4088 4092 4094 4095 4096 6144 7168 7680 7936 8064 8128 8160 8176 8184 8188 8190 8191 8192 12288 14336 15360 15872 16128 16256 16320
they are (of course) of the form (1<<m)*(1<<n-1)
with non-negative m
and n
.
static _Bool IsCompact(unsigned x)
{
return (x & x + (x & -x)) == 0;
}
x & -x
gives the lowest bit set in x
(or zero if x
is zero).
x + (x & -x)
converts the lowest string of consecutive 1s to a single 1 higher up (or wraps to zero).
x & x + (x & -x)
clears those 1 bits.
(x & x + (x & -x)) == 0
tests whether any other 1 bits remain.
-x
equals ~x+1
(for the int
in the question, we assume two’s complement, but unsigned
is preferable). After the bits are flipped in ~x
, adding 1 carries so that it flips back the low 1 bits in ~x
and the first 0 bit but then stops. Thus, the low bits of -x
up to and including its first 1 are the same as the low bits of x
, but all higher bits are flipped. (Example: ~10011100
gives 01100011
, and adding 1 gives 01100100
, so the low 100
are the same, but the high 10011
are flipped to 01100
.) Then x & -x
gives us the only bit that is 1 in both, which is that lowest 1 bit (00000100
). (If x
is zero, x & -x
is zero.)
Adding this to x
causes a carry through all the consecutive 1s, changing them to 0s. It will leave a 1 at the next higher 0 bit (or carry through the high end, leaving a wrapped total of zero) (10100000
.)
When this is ANDed with x
, there are 0s in the places where the 1s were changed to 0s (and also where the carry changed a 0 to a 1). So the result is not zero only if there is another 1 bit higher up.
There is actually no need to use any intrinsics.
First flip all the 0s before the first 1. Then test if the new value is a mersenne number. In this algo, zero is mapped to true.
bool has_compact_bits( unsigned const x )
{
// fill up the low order zeroes
unsigned const y = x | ( x - 1 );
// test if the 1's is one solid block
return not ( y & ( y + 1 ) );
}
Of course, if you want to use intrinsics, here is the popcount method:
bool has_compact_bits( unsigned const x )
{
size_t const num_bits = CHAR_BIT * sizeof(unsigned);
size_t const sum = __builtin_ctz(x) + __builtin_popcount(x) + __builtin_clz(z);
return sum == num_bits;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With