(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

10
答えは次のとおりです。stackoverflow.com
questions / 600293

2
ビットに従ってください。ちなみに、ゼロは2の累乗としてもカウントされます。この式(n & (n - 1)) == 0も機能します(最下位ビットが削除されます。ビットが残っていない場合は、最初に最大1ビットが設定されていました)。
ハロルド2011

3
はい、私はそのようなコードを使用することに対して有罪を認めます。2の補数演算を処理していて、さまざまな変換とオーバーフローの落とし穴を認識している限り、このようなトリックを実行できます。追加のクレジットについては、次に高い2の累乗、またはおそらく2の累乗-1に切り上げる方法を理解してください。これは、一部の四半期で驚くべき頻度で実行する必要があることです。
ホットリック2011

1
待ってください、最近、誰もがjava.util.Randomソースから読んでいますか?(私は数ヶ月前にそれを読みました、そしてそれ以来、SOでそれについてのいくつかの質問を覚えています。)
Mateen Ulhaq 2011

回答:


48

(0 & -0) == 00は2の累乗ではないため、説明は完全に正確ではありません。それを言うより良い方法は

((n & -n) == n) nが2の累乗、または2の累乗の負、またはゼロの場合。

nが2の累乗である場合、バイナリのnは単一の1の後にゼロが続きます。2の補数の-nは逆+1であるため、ビットは次のように整列します。

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

これが機能する理由を理解するには、2の補数を逆+1と見なします。 -n == ~n + 1

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

1を足して2の補数を得るときは、1をずっと持ち歩くからです。

nが2の累乗†以外の場合、2の補数はそのキャリーのために設定された最高ビットを持たないため、結果はビットが欠落します。

†-またはゼロまたは2の累乗の負数...上部で説明されているように。


そして、最下位の1ビットを分離するためのトリックがあります。
ホットリック2011

2
について(0 & -0) == 0、直前のステートメントif (n <= 0) throw ...です。つまり、テスト対象の数は、その時点で0(または負)になることはありません。
ユーザー

1
@マイケル、その通り。Random.java読んでいないことを批判せずにタイトルの質問に答えていました。
マイクサミュエル

1
@マイク、私はそれを理解しています。ただし、コード内のコメント(質問に含まれ、タイトルの質問の基礎となる)のステートメントは、直前に確立された前提条件のコンテキストで見られない場合、それ自体ではまったく成り立たないと思います。コードでそれに。ここに投稿された質問だけを見ると、タイプnが何であるかさえわかりません。私はこの仮定を確認していませんが、どういうわけか、adoubleが同じように動作するのではないかと疑っています。
ユーザー

3
@Michael、nこの質問には「java」タグがあるので、タイプにかなり良い範囲を設定できます。 Java&doubleまたはfloatJavaで定義されていません。整数型とブール値でのみ定義されます。以来-ブール型のために定義されていない、我々は安全に推測できn不可欠です。
マイクサミュエル

95

2の補数であるため、-nです~n+1

nが2の累乗の場合、1ビットしか設定されていません。したがって~n、1つを除くすべてのビットが設定されています。1を追加すると、特別なビットが再び設定され、それn & (that thing)がに等しくなることが保証されnます。

そのJavaソースの前の行で0と負の数が除外されているため、逆も当てはまります。n複数のビットが設定されている場合は、そのうちの1つがそのような最高のビットです。このビットは、それを「吸収」するためのより低いクリアビットがあるため、によって設定されません+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

2の補数表現では、2の累乗のユニークな点は、k番目のビット(n = 2 ^ k)を除いて、すべて0ビットで構成されていることです。

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

2の補数で負の値を取得するには、すべてのビットを反転して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

2の補数の計算:

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

AND 8    : 0b00001000

2の累乗の場合、1ビットのみが設定されるため、追加すると2 nのn番目のビットが設定されます(1つはn番目のビットに引き継がれます)。次に、2つの数字を入力すると、元の数字が返されます。AND

2の累乗ではない数値の場合、他のビットは反転されANDないため、元の数値は生成されません。


4

簡単に言うと、nが2の累乗である場合、つまり1ビットのみが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つの遺跡である手段のみビットの結果以来問題ではなく、オペレータがいること(&0の場合になります2ビットの1つがゼロです):

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

0

例を通して示されている:

16進数で8 = 0x000008

-8の16進数= 0xFFFFF8

8&-8 = 0x000008

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.