Pourquoi si (n & -n) == n alors n est une puissance de 2?

84

La ligne 294 de la source java.util.Random indique

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

Pourquoi est-ce?

David Weng
la source
2
La nouvelle balise doit être un indice. :)
bzlm
10
Voici la réponse: stackoverflow.com/questions/600293/…
Jacob Mattison
2
Suivez les bits. Incidemment, il compte également zéro comme une puissance de deux. La formule (n & (n - 1)) == 0fonctionne également (elle supprime le bit d'ordre le plus bas, s'il n'y a plus de bits, il y avait au plus 1 bit défini en premier lieu).
harold
3
Oui, je plaide coupable d'avoir utilisé un tel code. Il y a un certain nombre de trucs comme celui-ci que vous pouvez jouer, à condition que vous sachiez que vous avez affaire à l'arithmétique du complément de 2 et que vous restez conscient des divers pièges de conversion et de débordement. Pour obtenir un crédit supplémentaire, déterminez comment arrondir à la puissance immédiatement supérieure de deux, ou peut-être à la puissance de deux - 1 - ce qui doit être fait avec une fréquence surprenante dans certains trimestres.
Hot Licks
1
Attendez, est-ce que tout le monde lit de nos jours la source java.util.Random? (J'ai lu cela il y a quelques mois, et je me souviens de quelques questions à ce sujet sur SO depuis lors.)
Mateen Ulhaq

Réponses:

48

La description n'est pas tout à fait exacte car (0 & -0) == 0mais 0 n'est pas une puissance de deux. Une meilleure façon de dire que c'est

((n & -n) == n) quand n est une puissance de deux, ou le négatif d'une puissance de deux, ou zéro.

Si n est une puissance de deux, alors n en binaire est un simple 1 suivi de zéros. -n en complément à deux est l'inverse + 1 donc les bits s'alignent ainsi

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

Pour voir pourquoi ce travail, considérez le complément à deux comme inverse + 1, -n == ~n + 1

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

puisque vous portez celui tout au long de l'ajout d'un pour obtenir le complément à deux.

Si n était autre chose qu'une puissance de deux †, alors le résultat manquerait un peu parce que le complément à deux n'aurait pas le bit le plus élevé en raison de cette retenue.

† - ou zéro ou un négatif d'une puissance de deux ... comme expliqué en haut.

Mike Samuel
la source
Et il y a une astuce là-dedans pour isoler le bit le moins significatif.
Hot Licks
2
Quant à (0 & -0) == 0, la déclaration immédiatement précédente est if (n <= 0) throw .... Cela signifie que le nombre testé ne sera jamais égal à 0 (ou négatif) à ce stade.
utilisateur le
1
@Michael, tout à fait raison. Je répondais à la question dans le titre sans critiquer Random.javace que je n'ai pas lu.
Mike Samuel le
1
@Mike, je m'en rends compte; cependant, je ne pense pas que la déclaration dans le commentaire dans le code (qui est incluse dans la question et est la base de la question dans le titre) se tient tout à fait d'elle-même lorsqu'elle n'est pas vue dans le contexte des prérequis établis juste avant à cela dans le code. Si vous regardez uniquement la question telle que publiée ici, nous ne savons même rien de quel type nest; Je n'ai pas vérifié cette hypothèse, mais je doute en quelque sorte que un doublese comporte de la même manière.
utilisateur du
3
@Michael, on peut mettre de très bonnes limites sur le type de npuisque cette question a la balise "java". &n'est pas défini sur doubleou floatdans Java. Il n'est défini que sur les types entiers et booléens. Puisque -n'est pas défini pour les booléens, nous pouvons en déduire en toute sécurité nest intégrale.
Mike Samuel
95

Parce que dans le complément de 2, -nest ~n+1.

Si nest une puissance de 2, alors il n'a qu'un seul bit défini. Donc, ~ntous les bits sont définis sauf celui-là. Ajoutez 1 et vous définissez à nouveau le bit spécial, en vous assurant qu'il n & (that thing)est égal à n.

L'inverse est également vrai car les nombres 0 et négatifs ont été exclus par la ligne précédente dans cette source Java. Si nplus d'un bit est défini, l'un de ces bits est le bit le plus élevé. Ce bit ne sera pas défini par le+1 car il existe un bit clair inférieur pour "l'absorber":

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

Vous devez regarder les valeurs sous forme de bitmaps pour voir pourquoi cela est vrai:

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

Donc, ce n'est que si les deux champs sont 1 qu'un 1 sortira.

Maintenant -n fait un complément à 2. Il change tout le 0en 1et il ajoute 1.

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

pourtant

8 = 00001000
-8 = 11110111 + 1 = 11111000 

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

Seulement pour les puissances de 2 sera (n & -n)n.
En effet, une puissance de 2 est représentée comme un seul bit défini dans une longue mer de zéro. La négation donnera l'exact opposé, un seul zéro (à l'endroit où se trouvait le 1) dans une mer de 1. Ajouter 1 déplacera les inférieurs dans l'espace où se trouve le zéro.
Et les bits et (&) filtreront à nouveau le 1.

Johan
la source
8

Dans la représentation du complément à deux, la particularité des puissances de deux est qu'elles se composent des 0 bits, à l'exception du kème bit, où n = 2 ^ k:

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

Pour obtenir une valeur négative dans le complément à deux, vous retournez tous les bits et en ajoutez un. Pour les puissances de deux, cela signifie que vous obtenez un groupe de 1 sur la gauche jusqu'à et y compris le 1 bit qui était dans la valeur positive, puis un groupe de 0 sur la droite:

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

Vous pouvez facilement voir que le résultat des colonnes 2 et 4 sera le même que celui de la colonne 2.

Si vous regardez les autres valeurs absentes de ce graphique, vous pouvez voir pourquoi cela ne vaut rien d'autre que les puissances de deux:

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'aura (pour n> 0) que 1 bit défini, et ce bit sera le bit défini le moins significatif dans n. Pour tous les nombres qui sont des puissances de deux, le bit défini le moins significatif est le seul bit défini. Pour tous les autres nombres, il y a plus d'un bit défini, dont seul le moins significatif sera défini dans le résultat.

Éclipse
la source
4

C'est la propriété des puissances de 2 et du complément de leurs deux .

Par exemple, prenez 8:

8  = 0b00001000

-8 = 0b11111000

Calcul du complément à deux:

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

AND 8    : 0b00001000

Pour les puissances de 2, un seul bit sera mis à 1, donc l'ajout entraînera la mise à 1 du n ème bit de 2 n (celui qui continue de porter au n ème bit). Ensuite, lorsque vous ANDles deux numéros, vous récupérez l'original.

Pour les nombres qui ne sont pas des puissances de 2, les autres bits ne seront pas retournés, donc le ANDne donne pas le nombre d'origine.

Austin Salonen
la source
4

Simplement, si n est une puissance de 2, cela signifie qu'un seul bit est mis à 1 et les autres à 0:

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

and so on ...

et parce que -nest un complément à 2 de n(cela signifie que le seul bit qui est 1 reste tel quel et que les bits sur le côté gauche de ce bit sont assis à 1, ce qui n'a pas vraiment d'importance puisque le résultat de l'opérateur AND &sera 0 si l'un des deux bits est zéro):

000000...000010000...00000 <<< n
&
111111...111110000...00000 <<< -n
--------------------------
000000...000010000...00000 <<< n
Ing. Fouad
la source
0

Montré par exemple:

8 en hexadécimal = 0x000008

-8 en hexadécimal = 0xFFFFF8

8 et -8 = 0x000008

Jean B
la source