web-dev-qa-db-fra.com

Algorithme pour trouver la plus petite puissance de deux supérieure ou égale à une valeur donnée

J'ai besoin de trouver la plus petite puissance de deux supérieure ou égale à une valeur donnée. Jusqu'à présent, j'ai ceci:

int value = 3221; // 3221 is just an example, could be any number
int result = 1;

while (result < value) result <<= 1;

Cela fonctionne bien, mais semble un peu naïf. Existe-t-il un meilleur algorithme pour ce problème?

ÉDITER. Il y avait quelques suggestions de Nice Assembler, donc j'ajoute ces balises à la question.

47
Boyan

Voici mon préféré. À part la vérification initiale pour savoir si elle n'est pas valide (<0, que vous pourriez ignorer si vous saviez que seuls les nombres> = 0 seraient passés), elle n'a pas de boucles ou de conditions, et surpassera donc la plupart des autres méthodes. Ceci est similaire à la réponse d'Erickson, mais je pense que ma décrémentation de x au début et l'ajout de 1 à la fin est un peu moins gênant que sa réponse (et évite également le conditionnel à la fin).

/// Round up to next higher power of 2 (return x if it's already a power
/// of 2).
inline int
pow2roundup (int x)
{
    if (x < 0)
        return 0;
    --x;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    return x+1;
}
62
Larry Gritz
ceil(log2(value))

ilog2() peut être calculé en 3 instructions asm, par exemple http://www.asterisk.org/doxygen/1.4/log2comp_8h-source.html

19
jfs

Dans l'esprit de 0x5f3759df de Quake II et de la version IEEE de Bit Twiddling Hacks - cette solution se transforme en double pour extraire l'exposant comme moyen de calculer le plancher (lg2 ​​(n)). C'est un peu plus rapide que la solution acceptée et beaucoup plus rapide que la version Bit Twiddling IEEE car elle évite les calculs en virgule flottante. Tel qu'il est codé, il suppose qu'un double est un véritable flotteur IEEE * 8 sur une petite machine endienne.

int nextPow2(int n) 
{ 
    if ( n <= 1 ) return n;
    double d = n-1; 
    return 1 << ((((int*)&d)[1]>>20)-1022); 
} 

Edit: Ajoutez une version d'assemblage x86 optimisée avec l'aide d'un collègue. Un gain de vitesse de 4% mais toujours environ 50% plus lent qu'une version bsr (6 sec vs 4 sur mon portable pour n = 1..2 ^ 31-2).

int nextPow2(int n) 
{ 
    if ( n <= 1 ) return n;
    double d;
    n--;
    __asm {
      fild    n 
      mov     eax,4
      fstp    d 
      mov     ecx, dword ptr d[eax]
      sar     ecx,14h 
      rol     eax,cl 
  }
} 
11
Tony Lee

Sur le matériel Intel, l'instruction BSR est proche de ce que vous voulez - elle trouve le bit de jeu le plus significatif. Si vous devez être plus précis, vous pouvez alors vous demander si les bits restants sont précisément zéro ou non. J'ai tendance à supposer que d'autres processeurs auront quelque chose comme BSR - c'est une question à laquelle vous voulez une réponse pour normaliser un nombre. Si votre nombre est supérieur à 32 bits, vous effectuerez une analyse à partir de votre DWORD le plus significatif pour trouver le premier DWORD avec TOUT bits définis. Edsger Dijkstra remarquerait probablement que les "algorithmes" ci-dessus supposent que votre ordinateur utilise des chiffres binaires, tandis que de son genre de perspective "algorithmique" élevée, vous devriez penser aux machines Turing ou quelque chose - évidemment, je suis du style le plus pragmatique.

9
pngaz

Votre implémentation n'est pas naïve, c'est en fait la logique, sauf qu'elle est fausse - elle renvoie un négatif pour les nombres supérieurs à la moitié de la taille entière maximale.

En supposant que vous pouvez restreindre les nombres entre 0 et 2 ^ 30 (pour les entiers 32 bits), cela fonctionnera très bien et beaucoup plus rapidement que toutes les fonctions mathématiques impliquant des logarithmes.

Les entiers non signés fonctionneraient mieux mais vous vous retrouveriez avec une boucle infinie (pour les nombres supérieurs à 2 ^ 31) car vous ne pouvez jamais atteindre 2 ^ 32 avec l'opérateur <<.

6
paxdiablo

pow (2, ceil (log2 (valeur));

log2 (valeur) = log (valeur)/log (2);

5
Sorana

Une exploration des solutions possibles à des problèmes étroitement liés (c'est-à-dire arrondis au lieu de monter), dont beaucoup sont beaucoup plus rapides que l'approche naïve, est disponible sur la page Bit Twiddling Hacks , une excellente ressource pour faire le type d'optimisation que vous recherchez. La solution la plus rapide consiste à utiliser une table de recherche avec 256 entrées, ce qui réduit le nombre total d'opérations à environ 7, contre une moyenne de 62 (par une méthodologie de comptage d'opérations similaire) pour l'approche naïve. L'adaptation de ces solutions à votre problème est une question de comparaison et d'incrémentation.

4
Sparr

Voici une version modèle de la technique de décalage de bits.

template<typename T> T next_power2(T value)
{
    --value;
    for(size_t i = 1; i < sizeof(T) * CHAR_BIT; i*=2)
        value |= value >> i;
    return value+1;
}

Comme la boucle n'utilise que des constantes, elle est aplatie par le compilateur. (J'ai vérifié) La fonction est également à l'épreuve du temps.

En voici un qui utilise __builtin_clz. (Également à l'épreuve du temps)

template<typename T> T next_power2(T value)
{
    return 1 << ((sizeof(T) * CHAR_BIT) - __builtin_clz(value-1));
}
4
Zacrath

Que diriez-vous d'une version de modèle récursive pour générer une constante de compilation:

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundDown { enum{ value = Pow2RoundDown<(A | (A >> B)), B/2>::value }; };
template<uint32_t A>
struct Pow2RoundDown<A, 1> { enum{ value = (A | (A >> 1)) - ((A | (A >> 1)) >> 1) }; };

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundUp { enum{ value = Pow2RoundUp<((B == 16 ? (A-1) : A) | ((B == 16 ? (A-1) : A) >> B)), B/2>::value }; };
template<uint32_t A >
struct Pow2RoundUp<A, 1> { enum{ value = ((A | (A >> 1)) + 1) }; };

Peut être utilisé comme ceci:

Pow2RoundDown<3221>::value, Pow2RoundUp<3221>::value
3
duncan.forster

Vous ne dites pas vraiment ce que vous entendez par "meilleur algorithme" mais comme celui que vous présentez est parfaitement clair (s'il est quelque peu imparfait), je suppose que vous recherchez un algorithme plus efficace.

Larry Gritz a donné ce qui est probablement l'algorithme c/c ++ le plus efficace sans la surcharge d'une table de recherche et cela suffirait dans la plupart des cas (voir http://www.hackersdelight.org pour des algorithmes similaires ).

Comme mentionné ailleurs, la plupart des processeurs de nos jours ont des instructions machine pour compter le nombre de zéros non significatifs (ou renvoyer de manière équivalente le bit défini par ms), mais leur utilisation n'est pas portable et - dans la plupart des cas - n'en vaut pas la peine.

Cependant la plupart des compilateurs ont des fonctions "intrinsèques" qui permettent d'utiliser des instructions machine mais de manière plus portable.

Microsoft C++ a _BitScanReverse () et gcc fournit __builtin_clz () pour effectuer la majeure partie du travail efficacement.

3
Dipstick

Ma version du même:

int pwr2Test(size_t x) {
    return (x & (x - 1))? 0 : 1; 
}

size_t pwr2Floor(size_t x) {
    // A lookup table for rounding up 4 bit numbers to
    // the nearest power of 2.
    static const unsigned char pwr2lut[] = {
        0x00, 0x01, 0x02, 0x02,     //  0,  1,  2,  3
        0x04, 0x04, 0x04, 0x04,     //  4,  5,  6,  7
        0x08, 0x08, 0x08, 0x08,     //  8,  9, 10, 11
        0x08, 0x08, 0x08, 0x08      // 12, 13, 14, 15
    };

    size_t pwr2 = 0;                // The return value
    unsigned int i = 0;             // The nybble interator

    for( i = 0; x != 0; ++i ) {     // Iterate through nybbles
        pwr2 = pwr2lut[x & 0x0f];   // rounding up to powers of 2.
        x >>= 4;                    // (i - 1) will contain the
    }                               // highest non-zero nybble index.

    i = i? (i - 1) : i;
    pwr2 <<= (i * 4);
    return pwr2; 
}

size_t pwr2Size(size_t x) {
    if( pwr2Test(x) ) { return x; }
    return pwr2Floor(x) * 2; 
 }
1
natersoz

Je sais que c'est du downvote-bait, mais si le nombre est assez petit (comme 8 ou 16 bits), une recherche directe pourrait être plus rapide.

// fill in the table
unsigned short tab[65536];
unsigned short bit = tab[i];

Il pourrait être possible de l'étendre à 32 bits en faisant d'abord le mot haut puis le bas.

//
unsigned long bitHigh = ((unsigned long)tab[(unsigned short)(i >> 16)]) << 16;
unsigned long bitLow = 0;
if (bitHigh == 0){
    bitLow = tab[(unsigned short)(i & 0xffff)];
}
unsigned long answer = bitHigh | bitLow;

Ce n'est probablement pas mieux que les méthodes shift-or, mais peut-être pourrait être étendu à des tailles de mots plus grandes.

(En fait, cela donne le bit 1 le plus élevé. Il faudrait le décaler de 1 pour obtenir la puissance supérieure suivante de 2.)

1
Mike Dunlavey

Le code ci-dessous supprime à plusieurs reprises le bit le plus bas jusqu'à ce que le nombre soit une puissance de deux, puis double le résultat, sauf si le nombre est une puissance de deux pour commencer. Il a l'avantage de fonctionner dans un temps proportionnel au nombre de bits mis. Malheureusement, il a l'inconvénient d'exiger plus d'instructions dans presque tous les cas que le code de la question ou les suggestions de l'Assemblée. Je ne l'inclus que pour être complet.

int nextPow(int x) {
  int y = x
  while (x &= (x^(~x+1))) 
    y = x << 1;
  return y
}
1
DocMax

j'aime le changement.

je vais me contenter de

    int bufferPow = 1;
    while ( bufferPow<bufferSize && bufferPow>0) bufferPow <<= 1;

de cette façon, la boucle se termine toujours, et la partie après && est évaluée presque jamais. Et je ne pense pas que deux lignes valent un appel de fonction. Vous pouvez également faire un long ou court, selon votre jugement, et c'est très lisible. (si bufferPow devient négatif, j'espère que votre code principal sortira rapidement.)

Habituellement, vous ne calculez la puissance 2 qu'une seule fois au début d'un algorithme, donc l'optimisation serait de toute façon idiote. Cependant, je serais intéressé si quelqu'un s'ennuyait suffisamment pour un concours de vitesse ... en utilisant les exemples ci-dessus et 255 256 257 .. 4195 4196 4197

0
Kos Petoussis

Une fonction de log arbitraire peut être convertie en une base de log 2 en divisant par le log de 2:

$ /usr/local/pypy-1.9/bin/pypy
Python 2.7.2 (341e1e3821ff, Jun 07 2012, 15:38:48)
[PyPy 1.9.0 with GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``<arigato> yes but there is not
much sense if I explain all about today's greatest idea if tomorrow it's
completely outdated''
>>>> import math
>>>> print math.log(65535)/math.log(2)
15.9999779861
>>>> print math.log(65536)/math.log(2)
16.0
>>>>

Bien sûr, il ne sera pas précis à 100%, car il y a une arithmétique à virgule flottante.

0
user1277476