为什么(n&-n)== n那么n是2的幂?


84

java.util.Random源代码的第294行

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

为什么是这样?


2
新标签应为提示。:)
bzlm 2011年


2
跟随位。顺便说一下,它也将零视为2的幂。该公式(n & (n - 1)) == 0也起作用(它删除了最低位,如果没有剩余的位,那么最初最多设置1位)。
哈罗德

3
是的,我对使用此类代码表示认罪。只要您知道自己正在处理2的补码算术并且对各种转换和溢出陷阱有所了解,就可以使用许多这样的技巧。要获得额外的信用,请找出如何舍入到下一个更高的2的幂,或者也许是2的1的幂-在某些季度中需要以令人惊讶的频率进行操作。
热门点击“

1
等等,现在每个人都在阅读java.util.Random源代码吗?(几个月前我读过
这篇文章,

Answers:


48

该描述并不完全准确,因为(0 & -0) == 00不是2的幂。更好的说法是

((n & -n) == n) 当n是2的幂或2的幂的负数或零时。

如果n是2的幂,则二进制中的n是单个1,后跟零。-n为2的补数是倒数+ 1,因此位排成一行

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

要了解其工作原理,请将二进制补码视为逆+ 1。 -n == ~n + 1

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

因为当您添加一个得到两个的补码时,您会一直进行到一个。

如果n不是2的幂,则结果将丢失一点,因为由于该进位,两个补码的最高位不会设置。

†-或零或2的幂的负数...如顶部所述。


并且有一个技巧可以隔离最低有效的1位。
热门点击“

2
至于(0 & -0) == 0在紧接前一说法if (n <= 0) throw ...。这意味着被测数字在该点永远不会为0(或负数)。
用户

1
@迈克尔,非常正确。我在回答标题中的问题,但没有批评Random.java我没有读过。
Mike Samuel

1
@迈克,我意识到;但是,我认为在代码中的注释中(包括在问题中,是标题中问题的基础)中的语句在先前确定的先决条件的上下文中未完全独立在代码中。如果你看看在问题为张贴在这里,我们甚至不知道什么样的东西n是; 我没有检查过这个假设,但是以某种方式怀疑a的double行为方式是否相同。
用户

3
@Michael,n由于这个问题带有“ java”标记,因此我们可以对类型进行很好的限制。 &未在JavadoublefloatJava上定义。它仅在整数类型和布尔值上定义。由于-没有为布尔值定义,因此我们可以安全地推断出n整数。
Mike Samuel

95

因为是2的补码,-n所以~n+1

如果n为2的幂,则仅设置一位。因此,~n除一位外,所有位均已设置。加1,然后再次设置特殊位,确保n & (that thing)等于n

反之亦然,因为该Java源代码中的前一行排除了0和负数。如果n设置了多个位,则其中之一是最高的此类位。不会设置此位,+1因为还有一个较低的清除位可“吸收”它:

 n: 00001001000
~n: 11110110111
-n: 11110111000  // the first 0 bit "absorbed" the +1
        ^
        |
        (n & -n) fails to equal n at this bit.

13

您需要将这些值视为位图,以查看其正确性的原因:

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

因此,只有两个字段均为1时,才会出现1。

现在-n做2的补码。将所有更改01,然后加1。

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

然而

8 = 00001000
-8 = 11110111 + 1 = 11111000 

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

仅对于2的幂将为(n & -n)n。
这是因为2的幂表示为零的长整数中的单个置位。取反将产生完全相反的结果,即在1的海洋中产生一个零(在1以前为零。加1会将较低的1移到零所在的空间。
按位与(&)将再次过滤掉1。


8

用二的补码表示法,关于二的幂的唯一之处在于,它们由所有0位组成,除了第k位,其中n = 2 ^ k:

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

要获得二进制补码的负值,请翻转所有位并加1。对于2的幂,这意味着您在左侧得到一堆1(直到包括正值的1位),然后在右边得到一堆0:

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

您可以轻松地看到第2列和第4列的结果将与第2列相同。

如果您查看此图表中缺少的其他值,则可以看到为什么除了2的幂之外,它什么都不成立:

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(对于n> 0)将仅设置1位,并且该位将是n中的最低有效位。对于所有为2的幂的数字,最低有效位是唯一的设置位。对于所有其他数字,将设置一个以上的位,其中结果中将仅设置最低有效位。


4

它是2及其2的补数的幂的性质。

例如,取8:

8  = 0b00001000

-8 = 0b11111000

计算两者的补数:

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

AND 8    : 0b00001000

为2的幂,只有一个位将被设置,以便增加将导致第n的2位Ñ要设置(一个保持携带到n位)。然后,当您AND输入两个数字时,就可以得到原始数字。

对于不是2的幂的数字,其他位将不会被翻转,因此AND不会产生原始数字。


4

简而言之,如果n为2的幂,则意味着只有一位设置为1,其他位设置为0:

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

and so on ...

并且因为-n是2的补数n(这意味着唯一的1位保持原样,并且该位左侧的位为1,这实际上没有关系,因为AND运算符的结果为&0两位之一为零):

000000...000010000...00000 <<< n
&
111111...111110000...00000 <<< -n
--------------------------
000000...000010000...00000 <<< n

0

通过示例显示:

8进制= 0x000008

-8十六进制= 0xFFFFF8

8和-8 = 0x000008

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.