Чому якщо (n & -n) == n, то n дорівнює степеню 2?


84

Рядок 294 java.util.Випадкове джерело говорить

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

Чому це?


2
Новий тег повинен бути підказкою. :)
bzlm


2
Дотримуйтесь бітів. До речі, він також вважає нуль як ступінь двох. Формула (n & (n - 1)) == 0також працює (вона видаляє біт найнижчого порядку, якщо не залишилося бітів, тоді спочатку було встановлено щонайбільше 1 біт).
harold

3
Так, я визнаю себе винним у використанні такого коду. Існує низка таких хитрощів, у які ви можете грати, якщо ви знаєте, що маєте справу з арифметикою доповнення 2 і залишаєтесь в курсі різних підводних каменів. Щоб отримати додатковий кредит, з’ясуйте, як округлити до наступної вищої сили два, або, можливо, сили дві - 1 - речі, які потрібно робити з дивовижною частотою в деяких кварталах.
Hot Licks

1
Зачекайте, чи всі сьогодні читають із java.util.Random джерела? (Я прочитав це кілька місяців тому, і з тих пір пам’ятаю кілька запитань щодо цього на SO).
Матін Улхак

Відповіді:


48

Опис не зовсім точний, оскільки (0 & -0) == 00 не є рівнем двох. Кращий спосіб сказати, що це так

((n & -n) == n) коли n - ступінь двох, або мінус степеня двох, або нуль.

Якщо n дорівнює двійці, то n у двійковій одиниці - це одиниця 1, за якою слідують нулі. -n у додатку двох є оберненим + 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 було чим-небудь, крім степеня двох †, то результат би трохи відсутній, оскільки доповнення двох не мало б найвищого біта, встановленого через це перенесення.

† - або нуль, або мінус степеня два ... як пояснено вгорі.


І там є фокус, щоб виділити найменш значущий 1 біт.
Hot Licks

2
Що стосується (0 & -0) == 0, безпосередньо попередньої заяви є if (n <= 0) throw .... Це означає, що тестоване число ніколи не буде 0 (або негативним) на той момент.
користувач

1
@ Майкл, цілком правильно. Я відповідав на запитання в заголовку, не критикуючи Random.javaі не читав.
Mike Samuel

1
@Mike, я це розумію; однак, я не думаю, що висловлювання у коментарі у коді (який включений у питання та є основою для питання у заголовку) цілком самостійне, коли не розглядається в контексті передумов, встановлених безпосередньо до йому в коді. Якщо ви дивитесь лише на питання, розміщене тут, ми навіть нічого не знаємо про те, що це за тип n; Я не перевіряв цього припущення, але якось сумніваюся, що а doubleповодиться так само.
користувач

3
@Michael, ми можемо поставити досить добрі межі типу, nоскільки це запитання має тег "java". &не визначено на Java doubleабо floatв Java. Він визначається лише для цілочисельних типів та булевих. Оскільки -не визначено для булевих значень, ми можемо сміливо зробити висновок, що 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. Він змінює все 0на 1і додає 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.


8

У поданні доповнення двох, унікальність штук двох полягає в тому, що вони складаються з усіх 0 бітів, за винятком k-го біта, де n = 2 ^ k:

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

Щоб отримати від’ємне значення в додатку двох, переверніть усі біти і додайте один. Для степенів двох це означає, що ви отримуєте купу 1s ліворуч до 1 біта, який мав позитивне значення, а потім купу 0s праворуч:

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.

Якщо ви подивитесь на інші значення, відсутні на цій діаграмі, ви можете зрозуміти, чому це не стосується нічого, крім повноважень двох:

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. Для всіх чисел, які є степенями двійки, найменш значущим встановленим бітом є єдиний встановлений біт. Для всіх інших чисел існує більше одного набору бітів, з яких у результаті буде встановлено лише найменш значущі.


4

Це властивість степенів 2 та доповнення їх двох .

Наприклад, візьмемо 8:

8  = 0b00001000

-8 = 0b11111000

Розрахунок доповнення двох:

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

AND 8    : 0b00001000

Для ступенів 2 буде встановлено лише один біт, тому додавання призведе до встановлення n- го біта 2 n (той продовжує переносити до 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.