706 votes

Comment vérifier si un nombre est une puissance de 2 ?

Aujourd'hui, j'avais besoin d'un algorithme simple pour vérifier si un nombre est une puissance de 2.

L'algorithme doit être :

  1. Simple
  2. Correct pour tout ulong valeur.

J'ai trouvé cet algorithme simple :

private bool IsPowerOfTwo(ulong number)
{
    if (number == 0)
        return false;

    for (ulong power = 1; power > 0; power = power << 1)
    {
        // This for loop used shifting for powers of 2, meaning
        // that the value will become 0 after the last shift
        // (from binary 1000...0000 to 0000...0000) then, the 'for'
        // loop will break out.

        if (power == number)
            return true;
        if (power > number)
            return false;
    }
    return false;
}

Mais ensuite j'ai pensé, comment vérifier si log<code>2</code>x est un nombre exactement rond ? Mais quand j'ai vérifié pour 2^63+1, Math.Log a donné exactement 63 à cause de l'arrondi. J'ai donc vérifié si 2 à la puissance 63 est égal au nombre d'origine - et c'est le cas, car le calcul est fait en double et non en nombre exact :

private bool IsPowerOfTwo_2(ulong number)
{
    double log = Math.Log(number, 2);
    double pow = Math.Pow(2, Math.Round(log));
    return pow == number;
}

Ce retour true pour la valeur erronée donnée : 9223372036854775809 .

Existe-t-il un meilleur algorithme ?

1 votes

Je pense que la solution (x & (x - 1)) peut renvoyer des faux positifs lorsque X est une somme de puissances de deux, par ex. 8 + 16 .

46 votes

Tous les nombres peuvent être écrits comme une somme de puissances de deux, c'est pourquoi nous pouvons représenter n'importe quel nombre en binaire. En outre, votre exemple ne renvoie pas un faux positif, car 11000 & 10111 = 10000 != 0.

2 votes

@JoeBrown Il n'y a pas de faux positifs. En fait, l'expression renvoie la plus grande de toutes les sommes de deux puissances de deux.

1479voto

Greg Hewgill Points 356191

Il existe une astuce simple pour résoudre ce problème :

bool IsPowerOfTwo(ulong x)
{
    return (x & (x - 1)) == 0;
}

Pour être complet, le zéro n'est pas une puissance de deux. Si vous voulez prendre en compte ce cas limite, voici comment :

bool IsPowerOfTwo(ulong x)
{
    return (x != 0) && ((x & (x - 1)) == 0);
}

Explication

Tout d'abord l'opérateur binaire & bitwise de la définition MSDN :

Les opérateurs binaires & sont prédéfinis pour les types intégraux et bool. Pour types intégraux, & calcule le ET logique binaire de ses opérandes. Pour les opérandes bool, & calcule le ET logique de ses opérandes, c'est-à-dire que le résultat est vrai si et seulement si les deux opérandes sont vrais. c'est-à-dire que le résultat est vrai si et seulement si ses deux opérandes sont vrais.

Voyons maintenant comment tout cela se passe :

La fonction renvoie un booléen (vrai / faux) et accepte un paramètre entrant de type unsigned long (x, dans ce cas). Pour simplifier, supposons que quelqu'un a passé la valeur 4 et a appelé la fonction comme suit :

bool b = IsPowerOfTwo(4)

Maintenant, nous remplaçons chaque occurrence de x par 4 :

return (4 != 0) && ((4 & (4-1)) == 0);

Nous savons déjà que 4 != 0 correspond à true, jusque là tout va bien. Mais qu'en est-il :

((4 & (4-1)) == 0)

Cela se traduit bien sûr par ceci :

((4 & 3) == 0)

Mais qu'est-ce que c'est exactement 4&3 ?

La représentation binaire de 4 est 100 et la représentation binaire de 3 est 011 (rappelez-vous que le & prend la représentation binaire de ces nombres. Nous avons donc :

100 = 4
011 = 3

Imaginez que ces valeurs s'empilent comme une addition élémentaire. Le site & signifie que si les deux valeurs sont égales à 1, le résultat est 1, sinon il est 0. 1 & 1 = 1 , 1 & 0 = 0 , 0 & 0 = 0 et 0 & 1 = 0 . Alors on fait le calcul :

100
011
----
000

Le résultat est simplement 0. Nous revenons donc en arrière et regardons ce que notre déclaration de retour traduit maintenant :

return (4 != 0) && ((4 & 3) == 0);

Ce qui se traduit maintenant par :

return true && (0 == 0);

return true && true;

Nous savons tous que true && true est simplement true et cela montre que pour notre exemple, 4 est une puissance de 2.

67 votes

@Kripp : Le nombre sera de la forme binaire 1000...000. Lorsque vous le -1, il sera de la forme 0111...111. Ainsi, le binaire des deux nombres et le résultat serait 000000. Cela ne se produirait pas pour les nombres qui ne sont pas des puissances de deux, puisque 1010100 par exemple deviendrait 1010011, ce qui donnerait un (suite...)

49 votes

... Ce qui donne un 1010000 après le et binaire. Le seul faux positif serait 0, c'est pourquoi j'utiliserais : return (x != 0) && ((x & (x - 1)) == 0) ;

7 votes

Kripp, considérer (2:1, 10:1) (4:3, 100:11) (8:7, 1000:111) (16:15, 10000:1111) Vous voyez le schéma ?

107voto

Michael Burr Points 181287

Voici quelques sites qui documentent et expliquent cette méthode et d'autres astuces de manipulation des bits :

Et le grand-père d'entre eux, le livre "Hacker's Delight" par Henry Warren, Jr. :

Comme Page de Sean Anderson explique, l'expression ((x & (x - 1)) == 0) indique à tort que 0 est une puissance de 2. Il suggère d'utiliser :

(!(x & (x - 1)) && x)

pour corriger ce problème.

9 votes

0 est une puissance de 2... 2 ^ -inf = 0. ;) ;) ;)

4 votes

Puisqu'il s'agit d'un C# Dans ce fil de discussion, il convient de souligner que la dernière expression (de Sean Anderson) est illégale en C# puisque ! ne peut être appliqué qu'aux types booléens, et && exige également que les deux opérandes soient booléens (sauf que les opérateurs définis par l'utilisateur rendent d'autres choses possibles, mais cela n'est pas pertinent dans le cadre de l'initiative ulong .)

1 votes

catonmat.net/low-level-bit-hacks explique quelques bithacks apparentés avec des exemples sur 8 bits. Par exemple, isolez le 1 bit le plus à droite avec y = x & (-x) . Ce test n'est qu'un cas particulier d'effacement du bit le plus bas.

50voto

Andreas Petersson Points 8096

return (i & -i) == i

2 votes

j'ai vérifié son exactitude en java seulement, où il n'y a que des ints/longs signés. si c'est correct, ce serait la réponse supérieure. plus rapide+plus petit

8 votes

Il tire parti de l'une des propriétés de la notation par complément à deux : pour calculer la valeur négative d'un nombre, il faut effectuer une négation au niveau du bit et ajouter 1 au résultat. Le bit le moins significatif de i qui est défini sera également défini dans -i . Les bits situés en dessous seront à 0 (dans les deux valeurs) tandis que les bits situés au-dessus seront inversés les uns par rapport aux autres. La valeur de i & -i sera donc le bit le moins significatif dans i (qui est une puissance de deux). Si i a la même valeur, alors c'était le seul bit activé. Il échoue lorsque i est 0 pour la même raison que i & (i - 1) == 0 fait.

6 votes

Si i est un type non signé, le complément à deux n'a rien à voir avec cela. Vous tirez simplement parti des propriétés de l'arithmétique modulaire et de la fonction "bit à bit".

25voto

Matt Howells Points 20751
bool IsPowerOfTwo(ulong x)
{
    return x > 0 && (x & (x - 1)) == 0;
}

4 votes

Cette solution est meilleure parce qu'elle peut également traiter les nombres négatifs si ces derniers ont pu être introduits. (si long au lieu de ulong)

0 votes

Pourquoi une décimale passe-t-elle pour une puissance de deux dans ce cas ?

20voto

Rick Regan Points 1810

J'ai écrit un article à ce sujet récemment à http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/ . Il couvre le comptage des bits, la manière d'utiliser correctement les logarithmes, la vérification classique "x && !(x & (x - 1))", et d'autres encore.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X