Perché se (n & -n) == n allora n è una potenza di 2?


84

La riga 294 della fonte java.util.Random dice

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

Perchè è questo?


2
Il nuovo tag dovrebbe essere un suggerimento. :)
bzlm


2
Segui i bit. Per inciso, conta anche zero come potenza di due. La formula (n & (n - 1)) == 0funziona anche (rimuove il bit di ordine più basso, se non ci sono bit rimasti allora c'era al massimo 1 bit impostato al primo posto).
harold

3
Sì, mi dichiaro colpevole di utilizzare tale codice. Ci sono un certo numero di trucchi come questo che puoi giocare, purché tu sappia di avere a che fare con l'aritmetica del complemento di 2 e rimani consapevole delle varie trappole di conversione e di trabocco. Per un credito extra, cerca di capire come arrotondare per eccesso alla successiva potenza superiore di due, o forse alla potenza di due - 1 - cose che devono essere fatte con una frequenza sorprendente in alcuni trimestri.
Hot Licks

1
Aspetta, oggi leggono tutti dal sorgente java.util.Random? (L'ho letto qualche mese fa, e da allora ricordo alcune domande a riguardo su SO.)
Mateen Ulhaq

Risposte:


48

La descrizione non è del tutto accurata perché (0 & -0) == 0ma 0 non è una potenza di due. Un modo migliore per dirlo

((n & -n) == n) quando n è una potenza di due, o il negativo di una potenza di due, o zero.

Se n è una potenza di due, allora n in binario è un singolo 1 seguito da zeri. -n in complemento a due è l'inverso + 1 quindi i bit si allineano così

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

Per capire perché questo lavoro, considera il complemento a due come inverso + 1, -n == ~n + 1

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

dal momento che porti l'uno fino in fondo quando ne aggiungi uno per ottenere il complemento a due.

Se n fosse qualcosa di diverso da una potenza di due †, il risultato mancherebbe un po 'perché il complemento a due non avrebbe il bit più alto impostato a causa di quel riporto.

† - o zero o un negativo di una potenza di due ... come spiegato in alto.


E c'è un trucco per isolare il bit meno significativo 1.
Hot Licks

2
Quanto a (0 & -0) == 0, l'affermazione immediatamente precedente è if (n <= 0) throw .... Significa che il numero sotto test non sarà mai 0 (o negativo) a quel punto.
utente

1
@ Michael, hai ragione. Stavo rispondendo alla domanda nel titolo senza criticare Random.javache non ho letto.
Mike Samuel

1
@ Mike, me ne rendo conto; tuttavia, non credo che l'affermazione nel commento nel codice (che è inclusa nella domanda ed è la base per la domanda nel titolo) sia abbastanza da sola quando non vista nel contesto dei prerequisiti stabiliti appena prima ad esso nel codice. Se guardi solo la domanda come pubblicata qui, non sappiamo nemmeno di che tipo nsia; Non ho verificato questa ipotesi, ma in qualche modo dubito che a doublesi comporterebbe allo stesso modo.
utente

3
@ Michael, possiamo mettere limiti abbastanza buoni al tipo di npoiché questa domanda ha il tag "java". &non è definito su doubleo floatin Java. È definito solo su tipi interi e booleano. Poiché -non è definito per valori booleani, possiamo dedurre con sicurezza che nsia integrale.
Mike Samuel

95

Perché nel complemento di 2, -nè ~n+1.

Se nè una potenza di 2, ha solo un bit impostato. Quindi ~nha tutti i bit impostati tranne quello. Aggiungi 1 e imposti di nuovo il bit speciale, assicurandoti che n & (that thing)sia uguale a n.

Il contrario è vero anche perché 0 e numeri negativi sono stati esclusi dalla riga precedente in quella sorgente Java. Se nha più di un bit impostato, allora uno di questi è il bit più alto. Questo bit non verrà impostato da +1perché c'è un bit di clear più basso per "assorbirlo":

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

13

È necessario guardare i valori come bitmap per vedere perché questo è vero:

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

Quindi solo se entrambi i campi sono 1 verrà visualizzato un 1.

Ora -n fa un complemento di 2. Cambia tutto 0a 1e aggiunge 1.

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

però

8 = 00001000
-8 = 11110111 + 1 = 11111000 

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

Solo per potenze di 2 sarà (n & -n)n.
Questo perché una potenza di 2 è rappresentata come un singolo bit impostato in un lungo mare di zero. La negazione produrrà l'esatto opposto, un unico zero (nel punto in cui si trovava l'1) in un mare di 1. L'aggiunta di 1 sposterà quelli inferiori nello spazio in cui si trova lo zero.
E bit a bit e (&) filtreranno nuovamente l'1.


8

Nella rappresentazione in complemento a due, l'unica cosa sulle potenze di due, è che consistono di tutti i bit 0, ad eccezione del k-esimo bit, dove n = 2 ^ k:

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

Per ottenere un valore negativo in complemento a due, capovolgi tutti i bit e ne aggiungi uno. Per potenze di due, ciò significa che ottieni un gruppo di 1 a sinistra fino a includere l'1 bit che era nel valore positivo, e poi un gruppo di 0 a destra:

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

Puoi facilmente vedere che il risultato delle colonne 2 e 4 sarà lo stesso della colonna 2.

Se guardi gli altri valori mancanti da questo grafico, puoi capire perché questo non vale per nient'altro che i poteri di due:

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 (per n> 0) avrà sempre e solo 1 bit impostato, e quel bit sarà il bit impostato meno significativo in n. Per tutti i numeri che sono potenze di due, il bit impostato meno significativo è l'unico bit impostato. Per tutti gli altri numeri, è impostato più di un bit, di cui solo il meno significativo verrà impostato nel risultato.


4

È proprietà dei poteri di 2 e del loro complemento a due .

Ad esempio, prendi 8:

8  = 0b00001000

-8 = 0b11111000

Calcolo del complemento a due:

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

AND 8    : 0b00001000

Per potenze di 2, verrà impostato un solo bit, quindi aggiungendo verrà impostato l' ennesimo bit di 2 n (quello continua a portare all'ennesimo bit). Poi, quando hai ANDi due numeri, riavrai l'originale.

Per i numeri che non sono potenze di 2, gli altri bit non verranno capovolti, quindi ANDnon restituirà il numero originale.


4

Semplicemente, se n è una potenza di 2 significa che solo un bit è impostato a 1 e gli altri sono a 0:

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

and so on ...

e poiché -nè un complemento di 2 di n(ciò significa che l'unico bit che è 1 rimane com'è ei bit sul lato sinistro di quel bit sono posti a 1 che in realtà non ha importanza poiché il risultato dell'operatore AND &sarà 0 se uno dei due bit è zero):

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

0

Mostrato attraverso un esempio:

8 in esadecimale = 0x000008

-8 in esadecimale = 0xFFFFF8

8 e -8 = 0x000008

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.