Aujourd'hui, j'avais besoin d'un algorithme simple pour vérifier si un nombre est une puissance de 2.
L'algorithme doit être:
- Facile
- Corrigez pour n'importe quelle
ulong
valeur.
Je suis venu avec 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 alors j'ai pensé, que diriez-vous de vérifier si c'est un nombre exactement rond? Mais quand j'ai vérifié 2 ^ 63 + 1, je suis retourné exactement 63 à cause de l'arrondissement. J'ai donc vérifié si 2 à la puissance 63 est égal au nombre d'origine - et c'est parce que le calcul se fait en s et non en nombres exacts:log2 x
Math.Log
double
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
à la valeur donnée erronée: 9223372036854775809
.
Existe-t-il un meilleur algorithme?
(x & (x - 1))
peut renvoyer des faux positifs quandX
est une somme de puissances de deux, par exemple8 + 16
.Réponses:
Il existe une astuce simple pour ce problème:
Notez que cette fonction rendra compte
true
de0
, ce qui n'est pas une puissance de2
. Si vous souhaitez exclure cela, voici comment:Explication
D'abord et avant tout, l'opérateur binaire au niveau du bit de la définition MSDN:
Voyons maintenant comment tout cela se déroule:
La fonction retourne un booléen (true / false) et accepte un paramètre entrant de type unsigned long (x, dans ce cas). Supposons pour des raisons de simplicité que quelqu'un a passé la valeur 4 et a appelé la fonction comme ceci:
Maintenant, nous remplaçons chaque occurrence de x par 4:
Eh bien, nous savons déjà que 4! = 0 est vrai, jusqu'à présent tout va bien. Mais qu'en est-il:
Cela se traduit bien sûr par ceci:
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). Donc nous avons:
Imaginez que ces valeurs soient empilées comme un ajout élémentaire. L'
&
opérateur dit que si les deux valeurs sont égales à 1 , alors le résultat est 1, sinon il est égal à 0. Donc1 & 1 = 1
,1 & 0 = 0
,0 & 0 = 0
et0 & 1 = 0
. Nous faisons donc le calcul:Le résultat est simplement 0. Nous allons donc revenir en arrière et voir ce que notre déclaration de retour se traduit maintenant:
Ce qui se traduit maintenant par:
Nous savons tous que
true && true
c'est simpletrue
, et cela montre que pour notre exemple, 4 est une puissance de 2.la source
Certains sites qui documentent et expliquent cela et d'autres hacks de twiddling sont:
( http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 )
( http://bits.stephan-brumme.com/isPowerOfTwo.html )
Et leur grand-père, le livre "Hacker's Delight" de Henry Warren, Jr .:
Comme l' explique la page de Sean Anderson , l'expression
((x & (x - 1)) == 0)
indique à tort que 0 est une puissance de 2. Il suggère d'utiliser:pour corriger ce problème.
la source
!
ne peut être appliquée qu'aux types booléens, et&&
nécessite également que les deux opérandes soient booléens - (sauf que les opérateurs définis par l'utilisateur rendre d'autres choses possibles, mais ce n'est pas pertinent pourulong
.)return (i & -i) == i
la source
i
défini est également défini-i
. Les bits ci-dessous seront 0 (dans les deux valeurs) tandis que les bits au-dessus seront inversés l'un par rapport à l'autre. La valeur dei & -i
sera donc le bit de consigne le moins significatif dei
(qui est une puissance de deux). Sii
a la même valeur, c'était le seul bit défini. Il échoue quandi
est 0 pour la même raison quei & (i - 1) == 0
cela.i
est un type non signé, le complément à deux n'a rien à voir avec lui. Vous profitez simplement des propriétés de l'arithmétique modulaire et des bits et.i==0
(renvoie(0&0==0)
ce qui esttrue
). Cela devrait êtrereturn i && ( (i&-i)==i )
la source
J'ai récemment écrit un article à ce sujet sur http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/ . Il couvre le comptage de bits, comment utiliser correctement les logarithmes, la vérification classique "x &&! (X & (x - 1))" et bien d'autres.
la source
Voici une solution C ++ simple :
la source
__builtin_popcount
. Malheureusement, une famille de processeurs n'a pas encore une seule instruction d'assemblage pour ce faire (x86), c'est donc la méthode la plus rapide pour le comptage de bits. Sur toute autre architecture, il s'agit d'une seule instruction d'assemblage.popcnt
L'addendum suivant à la réponse acceptée peut être utile pour certaines personnes:
Une puissance de deux, exprimée en binaire, ressemblera toujours à 1 suivi de n zéros où n est supérieur ou égal à 0. Ex:
etc.
Lorsque nous soustrayons
1
de ce type de nombres, ils deviennent 0 suivi de n uns et encore n est le même que ci-dessus. Ex:etc.
Venir à l'essentiel
L'un de
x
s'aligne avec le zéro dex - 1
et tous les zéros dex
s'alignent avec ceux dex - 1
, ce qui fait que l'ET au niveau du bit donne 0. Et c'est ainsi que la réponse sur une seule ligne mentionnée ci-dessus est correcte.Ajoutant encore à la beauté de la réponse acceptée ci-dessus -
Donc, nous avons maintenant une propriété à notre disposition:
Une utilisation impressionnante de cette propriété est de savoir - Combien de 1 sont présents dans la représentation binaire d'un nombre donné? Le code court et doux pour le faire pour un entier donné
x
est:Un autre aspect des nombres qui peut être prouvé par le concept expliqué ci-dessus est "Chaque nombre positif peut-il être représenté comme la somme des puissances de 2?" .
Oui, chaque nombre positif peut être représenté comme la somme des puissances de 2. Pour tout nombre, prenez sa représentation binaire. Ex: Prenez le numéro
117
.la source
Après avoir posté la question, j'ai pensé à la solution suivante:
Nous devons vérifier si exactement l'un des chiffres binaires en est un. Donc, nous décalons simplement le chiffre d'un chiffre à la fois, et retournons
true
s'il est égal à 1. Si à tout moment nous arrivons par un nombre impair ((number & 1) == 1
), nous savons que le résultat estfalse
. Cela s'est avéré (en utilisant une référence) légèrement plus rapide que la méthode originale pour les (grandes) vraies valeurs et beaucoup plus rapide pour les fausses ou les petites valeurs.Bien sûr, la solution de Greg est bien meilleure.
la source
Et voici un algorithme général pour savoir si un nombre est une puissance d'un autre nombre.
la source
la source
c#
? Je suppose que c'estc++
commex
est renvoyé comme un booléen.Trouvez si le nombre donné est une puissance de 2.
la source
frexp
plutôt deslog
trucs désagréables si vous voulez utiliser des virgules flottantes.la source
C'est vraiment rapide. Il faut environ 6 minutes et 43 secondes pour vérifier tous les 2 ^ 32 entiers.
la source
Si
x
est une puissance de deux, son seul bit 1 est en positionn
. Cela signifie qu'ilx – 1
a un 0 en positionn
. Pour voir pourquoi, rappelez-vous comment fonctionne une soustraction binaire. En soustrayant 1 dex
, l'emprunt se propage jusqu'à la positionn
; bitn
devient 0 et tous les bits inférieurs deviennent 1. Maintenant, puisqu'ilx
n'a pas de 1 bit en commun avecx – 1
,x & (x – 1)
est 0 et!(x & (x – 1))
est vrai.la source
Un nombre est une puissance de 2 s'il ne contient qu'un seul bit défini. Nous pouvons utiliser cette propriété et la fonction générique
countSetBits
pour trouver si un nombre est une puissance de 2 ou non.Ceci est un programme C ++:
Nous n'avons pas besoin de vérifier explicitement que 0 est une puissance de 2, car il renvoie également False pour 0.
PRODUCTION
la source
while
au lieu d'unif
? Personnellement, je ne vois pas de raison, mais cela semble fonctionner. EDIT: - non ... il retournera 1 pour quelque chose de plus grand que0
!?Voici une autre méthode que j'ai conçue, dans ce cas en utilisant
|
au lieu de&
:la source
(x > 0)
peu ici?pour toute puissance de 2, ce qui suit est également valable.
n & (- n) == n
REMARQUE: échoue pour n = 0, donc besoin de le vérifier
Raison pour laquelle cela fonctionne est:
-n est le complément 2s de n. -n aura chaque bit à gauche du bit le plus à droite de n inversé par rapport à n. Pour les puissances de 2, il n'y a qu'un seul bit défini.
la source
Exemple
Algorithme
À l'aide d'un masque de bits, divisez
NUM
la variable en binaireIF R > 0 AND L > 0: Return FALSE
Sinon,
NUM
devient celui qui est non nulIF NUM = 1: Return TRUE
Sinon, passez à l'étape 1
Complexité
Heure ~
O(log(d))
oùd
est le nombre de chiffres binairesla source
Amélioration de la réponse de @ user134548, sans arithmétique de bits:
Cela fonctionne bien pour:
la source
Mark Gravell a suggéré ce si vous avez .NET Core 3, System.Runtime.Intrinsics.X86.Popcnt.PopCount
Instruction unique, plus rapide
(x != 0) && ((x & (x - 1)) == 0)
mais moins portable.la source
(x != 0) && ((x & (x - 1)) == 0)
? J'en doute, esp. sur les anciens systèmes où popcnt n'est pas disponibleEn C, j'ai testé l'
i && !(i & (i - 1)
astuce et l' ai comparée avec__builtin_popcount(i)
, en utilisant gcc sous Linux, avec l'indicateur -mpopcnt pour être sûr d'utiliser l'instruction POPCNT du CPU. Mon programme de test a compté le nombre d'entiers compris entre 0 et 2 ^ 31 qui étaient une puissance de deux.Au début, je pensais que
i && !(i & (i - 1)
c'était 10% plus rapide, même si j'ai vérifié que POPCNT était utilisé dans le démontage où je l'ai utilisé__builtin_popcount
.Cependant, j'ai réalisé que j'avais inclus une instruction if, et la prédiction de branche faisait probablement mieux sur la version bit twiddling. J'ai supprimé l'if et POPCNT s'est retrouvé plus rapidement, comme prévu.
Résultats:
Processeur Intel (R) Core (TM) i7-4771 max 3,90 GHz
Processeur AMD Ryzen Threadripper 2950X 16 cœurs max 3,50 GHz
Notez qu'ici, le processeur Intel semble légèrement plus lent que AMD avec le bit twiddling, mais a un POPCNT beaucoup plus rapide; l'AMD POPCNT ne fournit pas autant de boost.
popcnt_test.c:
Exécutez les tests:
la source
Je vois que de nombreuses réponses suggèrent de renvoyer n &&! (N & (n - 1)) mais d'après mon expérience, si les valeurs d'entrée sont négatives, elles renvoient de fausses valeurs. Je partagerai ici une autre approche simple car nous savons qu'une puissance de deux nombres n'a qu'un seul bit défini, donc simplement nous compterons le nombre de bits définis, cela prendra du temps O (log N).
Consultez cet article pour ne pas compter. de bits définis
la source
la source
Ce programme en java renvoie "vrai" si le nombre est une puissance de 2 et renvoie "faux" si ce n'est pas une puissance de 2
la source