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?
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?
(n & (n - 1)) == 0
cũ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).
Câu trả lời:
Mô tả này không hoàn toàn chính xác bởi vì (0 & -0) == 0
0 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.
(0 & -0) == 0
, báo cáo ngay trước đó là 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 đó.
Random.java
mà tôi chưa đọc.
n
là 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 double
sẽ hoạt động theo cùng một cách.
n
vì câu hỏi này có thẻ "java". &
không được định nghĩa trên double
hoặc float
trong 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 n
là tích phân.
Vì trong phần bù của 2, -n
là ~n+1
.
Nếu n
là lũy thừa của 2, thì nó chỉ có một bit được đặt. Vì vậy, ~n
có 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 n
có 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 +1
vì 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.
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ả 0
thành 1
và 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.
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ả.
Đó 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 AND
hai 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 AND
không mang lại số ban đầu.
Đơ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ì -n
là 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