Checking if an Integer is an Exact Binary Power

Solomon E via Digitalmars-d digitalmars-d at puremagic.com
Mon Apr 25 09:53:20 PDT 2016


On Monday, 25 April 2016 at 12:56:27 UTC, Andrei Alexandrescu 
wrote:
>>...
>
> That's smaller and faster, thanks. But how do you show it is 
> still correct? -- Andrei

First I wrote the following variants of isPow2A. It's more 
obvious that they're correct, but they didn't compile to fewer 
instructions.

bool isPow2D(T)(T x)
{
	return (x > 0) && !(x & (x - 1));
}

(The variant isPow2E avoids a jump instruction:)

bool isPow2E(T)(T x)
{
	return (x > 0) & !(x & (x - 1));
}

So at least I had some functions that I knew were doing what I 
wanted (handling negative values for sure) to test against.

Then I tried some other things that didn't work, then the bit 
shift variant, isPow2F. It seemed to work, I looked at at long 
list of results in the terminal and saw what the pattern is, but 
right, I shouldn't trust it without a proof.

(x & -x) evaluates to the value of the lowest bit set in x. 
Either (x & -x) == x, which is a power of 2, or (x & -x) == x 
minus the value of all the bits above the lowest bit set.

(x >>> 1) is equal to half of x or one less than half of x, or a 
positive number if x is negative.

For (x & -x) > (x >>> 1) to be true, that would either be because 
x is a power of 2, or else x - n > x/2 - 1 (or x - n > x/2, which 
is implied by that) where n is the value of the bits above the 
lowest bit set, which means n >= 2/3*x. So x - n > x/2 - 1 would 
be 1/3*x > x/2 - 1, which would be 0 > 1/6*x - 1. That's 
impossible for x > 6. For values of x under 6, the results check 
out case by case.

When x is negative in a signed type, the unsigned shift treats x 
as if it were an unsigned number. The only particular bit 
arrangement that's treated differently when using a signed type 
is that -(2^^32) isn't a power of two because it's negative, so 
the expression (x & -x) should evaluate to a negative value 
before the comparison.

That seems to work for a byte or a short, where (x & -x) comes 
out negative when at the min value.

I've written this clumsily because I'm not sure what sort of 
symbols to use and don't know how to write mathematical proofs 
for algorithms in programming in general, but I thought it was 
interesting that writing it down and double checking required 
showing how close the algorithm cuts it.

For example, x = 6144, (x & -x) == 2048, (x >>> 1) == 3072.

The cases for 0 to 6, when using signed types for x:
      algo B   algo F
x 0  B true   F false  (x & -x) == 0,  (x >>> 1) == 0
x 1  B true   F true   (x & -x) == 1,  (x >>> 1) == 0
x 2  B true   F true   (x & -x) == 2,  (x >>> 1) == 1
x 3  B false  F false  (x & -x) == 1,  (x >>> 1) == 1
x 4  B true   F true   (x & -x) == 4,  (x >>> 1) == 2
x 5  B false  F false  (x & -x) == 1,  (x >>> 1) == 2
x 6  B false  F false  (x & -x) == 2,  (x >>> 1) == 3



More information about the Digitalmars-d mailing list