84

Line 294 of java.util.Random source says

if ((n & -n) == n) // i.e., n is a power of 2
    // rest of the code

Why is this?

Mike Samuel
  • 109,453
  • 27
  • 204
  • 234
David Weng
  • 3,985
  • 12
  • 38
  • 49
  • 2
    The new tag should be a hint. :) – bzlm Sep 13 '11 at 16:47
  • 10
    Here's the answer: http://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2/600306#600306 – Jacob Mattison Sep 13 '11 at 16:49
  • 2
    Follow the bits. Incidentally, it also counts zero as a power of two. The formula `(n & (n - 1)) == 0` also works (it removes the lowest order bit, if there are no bits left then there was at most 1 bit set in the first place). – harold Sep 13 '11 at 16:54
  • 3
    Yep, I plead guilty to using such code. There are a number of tricks like this that you can play, so long as you know that you're dealing with 2's complement arithmetic and remain cognizant of the various conversion and overflow pitfalls. For extra credit figure out how to round up to the next higher power of two, or perhaps power of two - 1 -- things that need to be done with surprising frequency in some quarters. – Hot Licks Sep 13 '11 at 20:26
  • 1
    Wait, is *everyone* reading from the java.util.Random source nowadays? (I read that a few months ago, and I remember a few questions about it on SO since then.) – Mateen Ulhaq Sep 14 '11 at 04:55

7 Answers7

95

Because in 2's complement, -n is ~n+1.

If n is a power of 2, then it only has one bit set. So ~n has all the bits set except that one. Add 1, and you set the special bit again, ensuring that n & (that thing) is equal to n.

The converse is also true because 0 and negative numbers were ruled out by the previous line in that Java source. If n has more than one bit set, then one of those is the highest such bit. This bit will not be set by the +1 because there's a lower clear bit to "absorb" it:

 n: 00001001000
~n: 11110110111
-n: 11110111000  // the first 0 bit "absorbed" the +1
        ^
        |
        (n & -n) fails to equal n at this bit.
Steve Jessop
  • 257,525
  • 32
  • 431
  • 672
48

The description is not entirely accurate because (0 & -0) == 0 but 0 is not a power of two. A better way to say it is

((n & -n) == n) when n is a power of two, or the negative of a power of two, or zero.

If n is a power of two, then n in binary is a single 1 followed by zeros. -n in two's complement is the inverse + 1 so the bits lines up thus

 n      0000100...000
-n      1111100...000
 n & -n 0000100...000

To see why this work, consider two's complement as inverse + 1, -n == ~n + 1

n          0000100...000
inverse n  1111011...111
                     + 1
two's comp 1111100...000

since you carry the one all the way through when adding one to get the two's complement.

If n were anything other than a power of two† then the result would be missing a bit because the two's complement would not have the highest bit set due to that carry.

† - or zero or a negative of a power of two ... as explained at the top.

Mike Samuel
  • 109,453
  • 27
  • 204
  • 234
  • And there's a trick in there to isolate the least significant 1 bit. – Hot Licks Sep 13 '11 at 20:28
  • 2
    As for `(0 & -0) == 0`, [the immediately preceeding statement](http://developer.classpath.org/doc/java/util/Random-source.html#line.292) is `if (n <= 0) throw ...`. Meaning that the number under test will never be 0 (or negative) at that point. – user Sep 14 '11 at 09:49
  • 1
    @Michael, quite right. I was answering the question in the title not criticizing `Random.java` which I have not read. – Mike Samuel Sep 14 '11 at 16:41
  • 1
    @Mike, I realize that; however, I don't think the statement in the comment in the code (which is included in the question and is the basis for the question in the title) quite stands on its own when not seen in the context of the prerequisites established just prior to it in the code. If you look *only* at the question as posted here, we don't even know anything about what type `n` is; I haven't checked this assumption, but somehow doubt that a `double` would behave the same way. – user Sep 15 '11 at 11:50
  • 3
    @Michael, We can put pretty good bounds on the type of `n` since this question has the "java" tag. `&` is not defined on `double` or `float` in Java. It is only defined on integer types and boolean. Since `-` is not defined for booleans, we can safely infer `n` is integral. – Mike Samuel Sep 15 '11 at 16:19
13

You need to look at the values as bitmaps to see why this is true:

1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

So only if both fields are 1 will a 1 come out.

Now -n does a 2's complement. It changes all the 0 to 1 and it adds 1.

7 = 00000111
-1 = NEG(7) + 1 = 11111000 + 1 = 11111001

However

8 = 00001000
-8 = 11110111 + 1 = 11111000 

00001000  (8)
11111000  (-8)
--------- &
00001000 = 8.

Only for powers of 2 will (n & -n) be n.
This is because a power of 2 is represented as a single set bit in a long sea of zero's. The negation will yield the exact opposite, a single zero (in the spot where the 1 used to be) in a sea of 1's. Adding 1 will shift the lower ones into the space where the zero is.
And The bitwise and (&) will filter out the 1 again.

Johan
  • 71,222
  • 23
  • 174
  • 298
8

In two's complement representation, the unique thing about powers of two, is that they consist of all 0 bits, except for the kth bit, where n = 2^k:

base 2    base 10
000001 =  1 
000010 =  2
000100 =  4
     ...

To get a negative value in two's complement, you flip all the bits and add one. For powers of two, that means you get a bunch of 1s on the left up to and including the 1 bit that was in the positive value, and then a bunch of 0s on the right:

n   base 2  ~n      ~n+1 (-n)   n&-n  
1   000001  111110  111111      000001
2   000010  111101  111110      000010
4   000100  111011  111100      000100
8   001000  110111  111000      001000

You can easily see that the result of column 2 & 4 is going to be the same as column 2.

If you look at the other values missing from this chart, you can see why this doesn't hold for anything but the powers of two:

n   base 2  ~n      ~n+1 (-n)   n&-n  
1   000001  111110  111111      000001
2   000010  111101  111110      000010
3   000011  111100  111101      000001
4   000100  111011  111100      000100
5   000101  111010  111011      000001
6   000110  111001  111010      000010
7   000111  111000  111001      000001
8   001000  110111  111000      001000

n&-n will (for n > 0) only ever have 1 bit set, and that bit will be the least significant set bit in n. For all numbers that are powers of two, the least significant set bit is the only set bit. For all other numbers, there is more than one bit set, of which only the least significant will be set in the result.

Eclipse
  • 42,854
  • 19
  • 110
  • 166
4

It's property of powers of 2 and their two's complement.

For example, take 8:

8  = 0b00001000

-8 = 0b11111000

Calculating the two's complement:

Starting:  0b00001000
Flip bits: 0b11110111  (one's complement)
Add one:   0b11111000  

AND 8    : 0b00001000

For powers of 2, only one bit will be set so adding will cause the nth bit of 2n to be set (the one keeps carrying to the nth bit). Then when you AND the two numbers, you get the original back.

For numbers that aren't powers of 2, other bits will not get flipped so the AND doesn't yield the original number.

Community
  • 1
  • 1
Austin Salonen
  • 46,038
  • 15
  • 101
  • 135
4

Simply, if n is a power of 2 that means only one bit is set to 1 and the others are 0's:

00000...00001 = 2 ^ 0
00000...00010 = 2 ^ 1
00000...00100 = 2 ^ 2
00000...01000 = 2 ^ 3
00000...10000 = 2 ^ 4

and so on ...

and because -n is a 2's complement of n (that means the only bit which is 1 remains as it is and the bits on left side of that bit are sit to 1 which is actually doesn't matter since the result of AND operator & will be 0 if one of the two bits is zero):

000000...000010000...00000 <<< n
&
111111...111110000...00000 <<< -n
--------------------------
000000...000010000...00000 <<< n
Eng.Fouad
  • 107,075
  • 62
  • 298
  • 390
0

Shown through example:

8 in hex = 0x000008

-8 in hex = 0xFFFFF8

8 & -8 = 0x000008

John B
  • 30,460
  • 6
  • 67
  • 92