web-dev-qa-db-fra.com

Comment faire un entier log2 () en C++?

Dans les bibliothèques standard C++, je n'ai trouvé qu'une méthode de journalisation à virgule flottante. Maintenant, j'utilise log pour trouver le niveau d'un index dans une arborescence binaire (floor(2log(index))). 

Code (C++):

int targetlevel = int(log(index)/log(2));

Je crains que pour certains éléments Edge (les éléments de valeur 2 ^ n), log renvoie n-1.999999999999 au lieu de n.0. Cette peur est-elle correcte? Comment puis-je modifier ma déclaration afin qu'elle renvoie toujours une réponse correcte?

36
Peter Smit

Vous pouvez utiliser cette méthode à la place:

int targetlevel = 0;
while (index >>= 1) ++targetlevel;

Note: cela modifiera l'index. Si vous avez besoin de rien, créez un autre int temporaire.

La casse d'angle correspond à 0. Vous devriez probablement le vérifier séparément et lever une exception ou renvoyer une erreur si index == 0.

44
Igor Krivokon

Si vous êtes sur une plate-forme x86 ou x86-64 récente (et vous l’êtes probablement), utilisez l’instruction bsr qui renvoie la position du bit le plus élevé dans un entier non signé. Il s'avère que c'est exactement la même chose que log2 (). Voici une courte fonction C ou C++ qui appelle bsr à l'aide d'ASM en ligne:

#include <stdint.h>
static inline uint32_t log2(const uint32_t x) {
  uint32_t y;
  asm ( "\tbsr %1, %0\n"
      : "=r"(y)
      : "r" (x)
  );
  return y;
}
74
Matt J

Si vous voulez juste un journal entier rapide2 opération, la fonction mylog2() suivante le fera sans se soucier de la précision en virgule flottante:

#include <limits.h>

static unsigned int mylog2 (unsigned int val) {
    if (val == 0) return UINT_MAX;
    if (val == 1) return 0;
    unsigned int ret = 0;
    while (val > 1) {
        val >>= 1;
        ret++;
    }
    return ret;
}

#include <stdio.h>

int main (void) {
    for (unsigned int i = 0; i < 20; i++)
        printf ("%u -> %u\n", i, mylog2(i));
    putchar ('\n');
    for (unsigned int i = 0; i < 10; i++)
        printf ("%u -> %u\n", i+UINT_MAX-9, mylog2(i+UINT_MAX-9));
    return 0;
}

Le code ci-dessus contient également un petit harnais de test afin que vous puissiez vérifier le comportement:

0 -> 4294967295
1 -> 0
2 -> 1
3 -> 1
4 -> 2
5 -> 2
6 -> 2
7 -> 2
8 -> 3
9 -> 3
10 -> 3
11 -> 3
12 -> 3
13 -> 3
14 -> 3
15 -> 3
16 -> 4
17 -> 4
18 -> 4
19 -> 4

4294967286 -> 31
4294967287 -> 31
4294967288 -> 31
4294967289 -> 31
4294967290 -> 31
4294967291 -> 31
4294967292 -> 31
4294967293 -> 31
4294967294 -> 31
4294967295 -> 31

Il renverra UINT_MAX pour une valeur d'entrée égale à 0 indiquant un résultat non défini. Vous devez donc le vérifier (aucun entier non signé valide n'aura un logarithme aussi élevé).

À propos, il y a des bidouilles incroyablement rapides à faire exactement cela (trouver le bit le plus élevé défini dans un nombre de complément à 2) disponible à partir de ici . Je ne suggérerais pas de les utiliser à moins que la vitesse ne soit essentielle (je préfère la lisibilité), mais vous devriez être informé de leur existence.

18
paxdiablo

Logarithme entier de base 2

Voici ce que je fais pour les entiers non signés 64 bits. Ceci calcule le plancher du logarithme en base 2, ce qui équivaut à l'indice du bit de poids fort. Cette méthode est très rapide pour les grands nombres car elle utilise une boucle déroulée qui s'exécute toujours dans log₂64 = 6 étapes.

Essentiellement, elle soustrait des carrés progressivement plus petits dans la suite {0 ≤ k ≤ 5: 2 ^ (2 ^ k)} = {2³², 2¹⁶, 2⁸, 2⁴, 2², 2¹} = {4294967296, 65536, 256 , 16, 4, 2, 1} et additionne les exposants k des valeurs soustraites.

int uint64_log2(uint64_t n)
{
  #define S(k) if (n >= (UINT64_C(1) << k)) { i += k; n >>= k; }

  int i = -(n == 0); S(32); S(16); S(8); S(4); S(2); S(1); return i;

  #undef S
}

Notez que cela retourne –1 si l'entrée 0 invalide est donnée (ce que recherche la -(n == 0) initiale). Si vous ne comptez jamais l'invoquer avec n == 0, vous pouvez remplacer int i = 0; par l'initialiseur et ajouter assert(n != 0); à l'entrée de la fonction.

Logarithme entier de base 10

Les logarithmes de nombres entiers en base 10 peuvent être calculés de la même manière - le plus grand carré à tester étant 10¹⁶, car log₁₀2⁶⁴ 19,2659 ...

int uint64_log10(uint64_t n)
{
  #define S(k, m) if (n >= UINT64_C(m)) { i += k; n /= UINT64_C(m); }

  int i = -(n == 0);
  S(16,10000000000000000); S(8,100000000); S(4,10000); S(2,100); S(1,10);
  return i;

  #undef S
}
12
Todd Lehman

Cela a été proposé dans les commentaires ci-dessus. Utilisation des commandes intégrées de gcc:

static inline int log2i(int x) {
    assert(x > 0);

    return sizeof(int) * 8 - __builtin_clz(x) - 1;
}

static void test_log2i(void) {
    assert_se(log2i(1) == 0);
    assert_se(log2i(2) == 1);
    assert_se(log2i(3) == 1);
    assert_se(log2i(4) == 2);
    assert_se(log2i(32) == 5);
    assert_se(log2i(33) == 5);
    assert_se(log2i(63) == 5);
    assert_se(log2i(INT_MAX) == sizeof(int)*8-2);
}
5
zbyszek

Je n’ai jamais eu de problème avec la précision en virgule flottante de la formule utilisée (et une vérification rapide des nombres de 1 à 231 - 1 n'a trouvé aucune erreur), mais si vous êtes inquiet, vous pouvez utiliser cette fonction à la place, qui renvoie les mêmes résultats et qui est environ 66% plus rapide dans mes tests:

int HighestBit(int i){
    if(i == 0)
        return -1;

    int bit = 31;
    if((i & 0xFFFFFF00) == 0){
        i <<= 24;
        bit = 7;
    }else if((i & 0xFFFF0000) == 0){
        i <<= 16;
        bit = 15;
    }else if((i & 0xFF000000) == 0){
        i <<= 8;
        bit = 23;
    }

    if((i & 0xF0000000) == 0){
        i <<= 4;
        bit -= 4;
    }

    while((i & 0x80000000) == 0){
        i <<= 1;
        bit--;
    }

    return bit; 
}
3
P Daddy
int targetIndex = floor(log(i + 0.5)/log(2.0));
2
maxaposteriori

Ce n'est pas standard ou nécessairement portable, mais cela fonctionnera généralement. Je ne sais pas à quel point c'est efficace.

Convertissez l'index entier en un nombre à virgule flottante d'une précision suffisante. La représentation sera exacte, en supposant que la précision soit suffisante.

Recherchez la représentation des nombres à virgule flottante IEEE, extrayez l'exposant et apportez les modifications nécessaires pour trouver le journal de base 2.

2
David Thornley

Si vous utilisez C++ 11, vous pouvez en faire une fonction constexpr:

constexpr std::uint32_t log2(std::uint32_t n)
{
    return (n > 1) ? 1 + log2(n >> 1) : 0;
}
1
Kelmar

Il y a des réponses similaires ci-dessus. Cette réponse

  1. Fonctionne avec des nombres 64 bits
  2. Permet de choisir le type d'arrondissement et
  3. Comprend test/exemple de code

Les fonctions:

    static int floorLog2(int64_t x)
    { 
      assert(x > 0);
      return 63 - __builtin_clzl(x);
    }

    static int ceilLog2(int64_t x)
    {
      if (x == 1)
        // On my system __builtin_clzl(0) returns 63.  64 would make more sense   
        // and would be more consistent.  According to stackoverflow this result  
        // can get even stranger and you should just avoid __builtin_clzl(0).     
        return 0;
      else
        return floorLog2(x-1) + 1;
    }

Code de test:

for (int i = 1; i < 35; i++)
  std::cout<<"floorLog2("<<i<<") = "<<floorLog2(i)
           <<", ceilLog2("<<i<<") = "<<ceilLog2(i)<<std::endl;
1
Trade-Ideas Philip

Essentiellement déterminé quantifier les bits sont nécessaires pour représenter l'intervalle numérique: [0..maxvalue].

unsigned binary_depth( unsigned maxvalue )
   {
   int depth=0;
   while ( maxvalue ) maxvalue>>=1, depth++;
   return depth;
   }

Dans les sous-ensembles, les résultats obtenus correspondent à floor(log2(x)), que sont représentés de log2(x) quando x é u potência de 2.

xyy-1
-1
11
221
321
432
532
632
732
84

1
nobar

À quelle profondeur projetez-vous votre arbre? Vous pouvez définir une plage de say ... +/- 0.00000001 sur le nombre afin de le forcer à une valeur entière.

En fait, je ne suis pas certain que vous atteindrez un chiffre comme 1.99999999, car votre log2 ne devrait perdre aucune précision lors du calcul de 2 ^ n valeurs (car le nombre à virgule flottante est arrondi à la puissance 2 la plus proche.

0
CookieOfFortune

Cette fonction j'ai écrit ici

// The 'i' is for int, there is a log2 for double in stdclib
inline unsigned int log2i( unsigned int x )
{
  unsigned int log2Val = 0 ;
  // Count Push off bits to right until 0
  // 101 => 10 => 1 => 0
  // which means hibit was 3rd bit, its value is 2^3
  while( x>>=1 ) log2Val++;  // div by 2 until find log2.  log_2(63)=5.97, so
  // take that as 5, (this is a traditional integer function!)
  // eg x=63 (111111), log2Val=5 (last one isn't counted by the while loop)
  return log2Val ;
}
0
bobobobo

Réécrire la réponse de Todd Lehman pour qu'elle soit plus générique:

#include <climits>

template<typename N>
constexpr N ilog2(N n) {
    N i = 0;
    for (N k = sizeof(N) * CHAR_BIT; 0 < (k /= 2);) {
        if (n >= static_cast<N>(1) << k) { i += k; n >>= k; }
    }
    return i;
}

Clang avec -O3 déroule la boucle:

0000000100000f50    pushq   %rbp
0000000100000f51    movq    %rsp, %rbp
0000000100000f54    xorl    %eax, %eax
0000000100000f56    cmpl    $0xffff, %edi
0000000100000f5c    setg    %al
0000000100000f5f    shll    $0x4, %eax
0000000100000f62    movl    %eax, %ecx
0000000100000f64    sarl    %cl, %edi
0000000100000f66    xorl    %edx, %edx
0000000100000f68    cmpl    $0xff, %edi
0000000100000f6e    setg    %dl
0000000100000f71    leal    (,%rdx,8), %ecx
0000000100000f78    sarl    %cl, %edi
0000000100000f7a    leal    (%rax,%rdx,8), %eax
0000000100000f7d    xorl    %edx, %edx
0000000100000f7f    cmpl    $0xf, %edi
0000000100000f82    setg    %dl
0000000100000f85    leal    (,%rdx,4), %ecx
0000000100000f8c    sarl    %cl, %edi
0000000100000f8e    leal    (%rax,%rdx,4), %eax
0000000100000f91    xorl    %edx, %edx
0000000100000f93    cmpl    $0x3, %edi
0000000100000f96    setg    %dl
0000000100000f99    leal    (%rdx,%rdx), %ecx
0000000100000f9c    sarl    %cl, %edi
0000000100000f9e    leal    (%rax,%rdx,2), %ecx
0000000100000fa1    xorl    %eax, %eax
0000000100000fa3    cmpl    $0x1, %edi
0000000100000fa6    setg    %al
0000000100000fa9    orl %ecx, %eax
0000000100000fab    popq    %rbp

Lorsque n est constant, le résultat est calculé en temps de compilation.

0
wonder.mice

Étant donné le fonctionnement des nombres en virgule flottante (grossièrement, mantisse * 2 ^ exposant), alors tout nombre inférieur à 2 ^ 127 représentant une puissance de 2 sera représenté exactement sans erreur.

Cela donne une solution triviale mais plutôt compliquée: interprétez le motif binaire du nombre à virgule flottante comme un entier et regardez simplement l'exposant. C'est la solution de David Thornley ci-dessus.

float f = 1;
for (int i = 0; i < 128; i++)
{
    int x = (*(int*)(&f)>>23) - 127;
    int l = int(log(f) / log(2));

    printf("i = %d, log = %d, f = %f quick = %d\n",
        i, l, f, x);
    f *= 2;
}

Il n’est pas vrai que tout entier puisse être représenté par un nombre à virgule flottante - seuls ceux avec moins de bits que la mantisse peut en représenter. Dans les flotteurs 32 bits, cela vaut 23 bits.

0
GizmoGremlin

Ceci est un ancien post mais je partage mon algorithme à une ligne:

unsigned uintlog2(unsigned x)
{
   unsigned l;
   for(l=0; x>1; x>>=1, l++);
   return l;
} 
0
Andrea993