Tại sao nếu (n & -n) == n thì n là lũy thừa của 2?


84

Dòng 294 của nguồn java.util.Random cho biết

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

Tại sao thế này?


2
Thẻ mới phải là một gợi ý. :)
bzlm


2
Làm theo các bit. Ngẫu nhiên, nó cũng được tính là số 0 như một lũy thừa của hai. Công thức (n & (n - 1)) == 0cũng hoạt động (nó loại bỏ bit thứ tự thấp nhất, nếu không còn bit nào thì có nhiều nhất 1 bit được đặt ở vị trí đầu tiên).
harold

3
Đúng, tôi nhận tội khi sử dụng mã như vậy. Có một số thủ thuật như thế này mà bạn có thể chơi, miễn là bạn biết rằng bạn đang xử lý số học bổ sung của 2 và vẫn nhận thức được các cạm bẫy chuyển đổi và tràn khác nhau. Để có thêm tín dụng, hãy tìm cách làm tròn thành lũy thừa tiếp theo cao hơn của hai, hoặc có thể là lũy thừa của hai - 1 - những việc cần được thực hiện với tần suất đáng ngạc nhiên trong một số quý.
Hot Licks,

1
Chờ đã, ngày nay mọi người có đang đọc từ nguồn java.util.Random không? (Tôi đọc mà cách đây vài tháng, và tôi nhớ một số câu hỏi về nó trên SO kể từ đó.)
Mateen Ulhaq

Câu trả lời:


48

Mô tả này không hoàn toàn chính xác bởi vì (0 & -0) == 00 không phải là lũy thừa của hai. Một cách tốt hơn để nói nó là

((n & -n) == n) khi n là lũy thừa của hai, hoặc âm của lũy thừa hai, hoặc bằng không.

Nếu n là lũy thừa của hai, thì n trong hệ nhị phân là số 1 duy nhất theo sau là số không. -n trong phần bù của hai là nghịch đảo + 1 vì vậy các bit xếp hàng như vậy

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

Để biết tại sao điều này hoạt động, hãy coi phần bù của hai là nghịch đảo + 1, -n == ~n + 1

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

vì bạn thực hiện một cái suốt khi thêm một cái để có được phần bù của hai cái.

Nếu n là bất kỳ thứ gì khác với lũy thừa của hai † thì kết quả sẽ thiếu một chút vì phần bù của hai sẽ không có bit cao nhất được đặt do mang đó.

† - hoặc không hoặc âm của lũy thừa hai ... như đã giải thích ở trên.


Và có một mẹo trong đó để cô lập 1 bit ít quan trọng nhất.
Hot Licks,

2
Đối với (0 & -0) == 0, báo cáo ngay trước đóif (n <= 0) throw .... Có nghĩa là con số được kiểm tra sẽ không bao giờ là 0 (hoặc âm) tại thời điểm đó.
người dùng

1
@Michael, khá đúng. Tôi đã trả lời câu hỏi trong tiêu đề không chỉ trích Random.javamà tôi chưa đọc.
Mike Samuel

1
@Mike, tôi nhận ra rằng; tuy nhiên, tôi không nghĩ rằng tuyên bố trong nhận xét trong mã (được bao gồm trong câu hỏi và là cơ sở cho câu hỏi trong tiêu đề) hoàn toàn tự đứng khi không được nhìn thấy trong bối cảnh của các điều kiện tiên quyết được thiết lập ngay trước đó với nó trong mã. Nếu bạn chỉ nhìn vào câu hỏi được đăng ở đây, chúng tôi thậm chí không biết bất cứ điều gì về loại nlà gì ; Tôi đã không kiểm tra giả định này, nhưng bằng cách nào đó nghi ngờ rằng a doublesẽ hoạt động theo cùng một cách.
người dùng

3
@Michael, Chúng tôi có thể đặt giới hạn khá tốt về loại nvì câu hỏi này có thẻ "java". &không được định nghĩa trên doublehoặc floattrong Java. Nó chỉ được định nghĩa trên kiểu số nguyên và boolean. Vì -không được định nghĩa cho boolean, chúng ta có thể suy ra một cách an toàn nlà tích phân.
Mike Samuel

95

Vì trong phần bù của 2, -n~n+1.

Nếu nlà lũy thừa của 2, thì nó chỉ có một bit được đặt. Vì vậy, ~ncó tất cả các bit được thiết lập ngoại trừ cái đó. Thêm 1, và bạn đặt lại bit đặc biệt, đảm bảo rằng n & (that thing)bằng n.

Điều ngược lại cũng đúng vì 0 và số âm đã bị dòng trước đó trong nguồn Java đó loại trừ. Nếu ncó nhiều hơn một bit được thiết lập, thì một trong số đó là bit cao nhất. Bit này sẽ không được thiết lập bởi +1vì có một bit rõ ràng thấp hơn để "hấp thụ" nó:

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

13

Bạn cần phải xem các giá trị dưới dạng bitmap để xem tại sao điều này đúng:

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

Vì vậy, chỉ khi cả hai trường là 1 thì 1 sẽ xuất hiện.

Bây giờ -n thực hiện phần bù của 2. Nó thay đổi tất cả 0thành 1và nó thêm 1.

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

Tuy nhiên

8 = 00001000
-8 = 11110111 + 1 = 11111000 

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

Chỉ cho lũy thừa của 2 sẽ (n & -n)là n.
Điều này là do lũy thừa của 2 được biểu diễn dưới dạng một bit tập hợp duy nhất trong một biển dài các số 0. Sự phủ định sẽ mang lại điều ngược lại hoàn toàn, một số 0 duy nhất (ở vị trí mà số 1 từng là) trong một biển số 1. Thêm 1 sẽ chuyển những cái thấp hơn vào khoảng trống có số 0.
Và Bitwise và (&) sẽ lọc ra 1 lần nữa.


8

Trong biểu diễn phần bù của hai, điều độc đáo về lũy thừa của hai, là chúng bao gồm tất cả các bit 0, ngoại trừ bit thứ k, trong đó n = 2 ^ k:

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

Để nhận giá trị âm trong phần bù của hai, bạn lật tất cả các bit và thêm một. Đối với lũy thừa của hai, điều đó có nghĩa là bạn nhận được một loạt các số 1 ở bên trái và bao gồm cả bit 1 có giá trị dương và sau đó là một loạt các số 0 ở bên phải:

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

Bạn có thể dễ dàng thấy rằng kết quả của cột 2 & 4 sẽ giống như cột 2.

Nếu bạn nhìn vào các giá trị khác bị thiếu trong biểu đồ này, bạn có thể thấy lý do tại sao điều này không giữ cho bất cứ điều gì ngoài quyền hạn của hai:

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 sẽ (đối với n> 0) chỉ có 1 bit được đặt và bit đó sẽ là bit được đặt ít quan trọng nhất trong n. Đối với tất cả các số là lũy thừa của hai, bit đặt nhỏ nhất có nghĩa là bit đặt duy nhất. Đối với tất cả các số khác, có nhiều hơn một bộ bit, trong đó chỉ số có ý nghĩa nhỏ nhất sẽ được đặt trong kết quả.


4

Đó là tài sản của lũy thừa của 2 và phần bù của hai .

Ví dụ, lấy 8:

8  = 0b00001000

-8 = 0b11111000

Tính phần bù của hai:

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

AND 8    : 0b00001000

Đối với lũy thừa của 2, chỉ một bit sẽ được thiết lập vì vậy việc thêm vào sẽ làm cho bit thứ n của 2 n được đặt (cái tiếp tục mang đến bit thứ n ). Sau đó, khi bạn ANDhai số, bạn nhận lại ban đầu.

Đối với các số không phải là lũy thừa của 2, các bit khác sẽ không bị lật nên ANDkhông mang lại số ban đầu.


4

Đơn giản, nếu n là lũy thừa của 2 nghĩa là chỉ một bit được đặt thành 1 và các bit khác là 0:

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

and so on ...

và bởi vì -nlà phần bù của 2 của n(điều đó có nghĩa là bit duy nhất là 1 vẫn giữ nguyên như vậy và các bit ở bên trái của bit đó là 1, điều này thực sự không quan trọng vì kết quả của toán tử AND &sẽ là 0 nếu một trong hai bit là 0):

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

0

Được thể hiện qua ví dụ:

8 in hex = 0x000008

-8 trong hệ thập lục phân = 0xFFFFF8

8 & -8 = 0x000008

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.