web-dev-qa-db-fra.com

Calculer la base de bûche rapide 2 plafond

Quel est un moyen rapide de calculer le (long int) ceiling(log_2(i)), où l'entrée et la sortie sont des entiers 64 bits? Les solutions pour les entiers signés ou non signés sont acceptables. Je soupçonne que la meilleure façon de procéder sera une méthode peu rébarbative semblable à celles trouvées ici , mais plutôt que de tenter la mienne, j'aimerais utiliser quelque chose qui a déjà été testé. Une solution générale fonctionnera pour toutes les valeurs positives.

Par exemple, les valeurs pour 2,3,4,5,6,7,8 sont 1,2,2,3,3,3,3,3 

Edit: Jusqu'à présent, le meilleur moyen semble être de calculer la base 2 du journal entier/plancher (la position du bit de poids fort) en utilisant un nombre quelconque de méthodes bithacks ou de registre existantes, puis d'en ajouter une si l'entrée n'est pas une puissance de deux. La vérification binaire rapide pour des puissances de deux est (n&(n-1)).

Edit 2: Les sections 5-3 et 11-4 de Hacker's Delight de Henry S. Warren constituent une bonne source de logarithmes d’entier et de méthodes zéros non significatifs. C'est le traitement le plus complet que j'ai trouvé.

Edit 3: Cette technique semble prometteuse: https://stackoverflow.com/a/51351885/365478

36
kevinlawler

Cet algorithme a déjà été publié, mais l'implémentation suivante est très compacte et doit être optimisée en code sans branche.

int ceil_log2(unsigned long long x)
{
  static const unsigned long long t[6] = {
    0xFFFFFFFF00000000ull,
    0x00000000FFFF0000ull,
    0x000000000000FF00ull,
    0x00000000000000F0ull,
    0x000000000000000Cull,
    0x0000000000000002ull
  };

  int y = (((x & (x - 1)) == 0) ? 0 : 1);
  int j = 32;
  int i;

  for (i = 0; i < 6; i++) {
    int k = (((x & t[i]) == 0) ? 0 : j);
    y += k;
    x >>= k;
    j >>= 1;
  }

  return y;
}


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  printf("%d\n", ceil_log2(atol(argv[1])));

  return 0;
}
18
dgobbi

Si vous pouvez vous limiter à gcc, il existe un ensemble de fonctions intégrées qui renvoient le nombre de bits initiaux nuls et peuvent être utilisées pour faire ce que vous voulez avec un peu de travail:

int __builtin_clz (unsigned int x)
int __builtin_clzl (unsigned long)
int __builtin_clzll (unsigned long long)
19
ergosys

Si vous compilez des processeurs 64 bits sous Windows, cela devrait fonctionner. _BitScanReverse64 est une fonction intrinsèque.

#include <intrin.h>
__int64 log2ceil( __int64 x )
{
  unsigned long index;
  if ( !_BitScanReverse64( &index, x ) )
     return -1LL; //dummy return value for x==0

  // add 1 if x is NOT a power of 2 (to do the ceil)
  return index + (x&(x-1)?1:0);
}

Pour 32 bits, vous pouvez émuler _BitScanReverse64, avec 1 ou 2 appels à _BitScanReverse. Vérifiez les 32 bits supérieurs de x, ((long *) & x) [1], puis les 32 bits inférieurs si nécessaire, ((long *) & x) [0].

10
Tom Sirgedas
#include "stdafx.h"
#include "assert.h"

int getpos(unsigned __int64 value)
{
    if (!value)
    {
      return -1; // no bits set
    }
    int pos = 0;
    if (value & (value - 1ULL))
    {
      pos = 1;
    }
    if (value & 0xFFFFFFFF00000000ULL)
    {
      pos += 32;
      value = value >> 32;
    }
    if (value & 0x00000000FFFF0000ULL)
    {
      pos += 16;
      value = value >> 16;
    }
    if (value & 0x000000000000FF00ULL)
    {
      pos += 8;
      value = value >> 8;
    }
    if (value & 0x00000000000000F0ULL)
    {
      pos += 4;
      value = value >> 4;
    }
    if (value & 0x000000000000000CULL)
    {
      pos += 2;
      value = value >> 2;
    }
    if (value & 0x0000000000000002ULL)
    {
      pos += 1;
      value = value >> 1;
    }
    return pos;
}

int _tmain(int argc, _TCHAR* argv[])
{    
    assert(getpos(0ULL) == -1); // None bits set, return -1.
    assert(getpos(1ULL) == 0);
    assert(getpos(2ULL) == 1);
    assert(getpos(3ULL) == 2);
    assert(getpos(4ULL) == 2);
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos(1ULL << k);
        assert(pos == k);
    }
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos( (1ULL << k) - 1);
        assert(pos == (k < 2 ? k - 1 : k) );
    }
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos( (1ULL << k) | 1);
        assert(pos == (k < 1 ? k : k + 1) );
    }
    for (int k = 0; k < 64; ++k)
    {
        int pos = getpos( (1ULL << k) + 1);
        assert(pos == k + 1);
    }
    return 0;
}
5
rwong

En utilisant les commandes gcc mentionnées par @egosys, vous pouvez créer des macros utiles. Pour un calcul rapide et approximatif du plancher (log2 (x)), vous pouvez utiliser:

#define FAST_LOG2(x) (sizeof(unsigned long)*8 - 1 - __builtin_clzl((unsigned long)(x)))

Pour un ceil similaire (log2 (x)), utilisez:

#define FAST_LOG2_UP(x) (((x) - (1 << FAST_LOG2(x))) ? FAST_LOG2(x) + 1 : FAST_LOG2(x))

Ce dernier peut être optimisé davantage en utilisant davantage de particularités de gcc afin d'éviter le double appel à la commande intégrée, mais je ne suis pas sûr que vous en ayez besoin ici.

5

L'extrait de code suivant est un moyen sûr et portable d'étendre les méthodes en clair, telles que @ dgobbi, pour qu'il utilise les éléments intrinsèques du compilateur lors de la compilation à l'aide de compilateurs de support (Clang). Si vous le placez en haut de la méthode, celle-ci utilisera la commande intégrée lorsqu'elle sera disponible. Lorsque la fonction intégrée n'est pas disponible, la méthode revient au code C standard.

#ifndef __has_builtin
#define __has_builtin(x) 0
#endif

#if __has_builtin(__builtin_clzll) //use compiler if possible
  return ((sizeof(unsigned long long) * 8 - 1) - __builtin_clzll(x)) + (!!(x & (x - 1)));
#endif
4
kevinlawler

L’approche la plus rapide que je connaisse utilise un log2 rapide qui arrondit les valeurs, ainsi qu’un ajustement inconditionnel combiné de la valeur en entrée avant et après pour traiter les majuscules comme dans lg_down() ci-dessous.

/* base-2 logarithm, rounding down */
static inline uint64_t lg_down(uint64_t x) {
  return 63U - __builtin_clzl(x);
}

/* base-2 logarithm, rounding up */
static inline uint64_t lg_up(uint64_t x) {
  return lg_down(x - 1) + 1;
}

Fondamentalement, ajouter 1 au résultat arrondi est déjà correct pour toutes les valeurs sauf les puissances exactes de deux (puisque dans ce cas, les approches floor et ceil devraient renvoyer la même réponse), il est donc suffisant de soustraire 1 de la valeur d'entrée pour traiter ce cas (cela ne change pas la réponse pour les autres cas) et ajoute un au résultat. 

Ceci est généralement légèrement plus rapide que les approches qui ajustent la valeur en recherchant explicitement les puissances exactes de deux (par exemple, en ajoutant un terme !!(x & (x - 1))). Cela évite les comparaisons et les opérations ou branches conditionnelles, a plus de chance de simplement en aligner, est plus sujet à la vectorisation, etc.

Cela repose sur la fonctionnalité "nombre de bits de début de ligne" proposée par la plupart des processeurs utilisant le __builtin_clzl intégré de clang/icc/gcc, mais d'autres plates-formes offrent quelque chose de similaire (par exemple, la variable BitScanReverse intrinsèque dans Visual Studio).

Malheureusement, beaucoup renvoient la mauvaise réponse pour log(1), car cela conduit à __builtin_clzl(0), ce qui est un comportement indéfini basé sur la documentation gcc. Bien entendu, la fonction générale "nombre de zéros au début" a un comportement parfaitement défini à zéro, mais gcc est intégré de cette manière car avant l'extension BMI ISA sur x86, il aurait utilisé le paramètre Instruction bsr qui a elle-même un comportement indéfini.

Vous pouvez contourner ce problème si vous savez que vous avez l'instruction lzcnt en utilisant directement la variable lzcnt. La plupart des plates-formes autres que x86 n'ont jamais commis l'erreur bsr et proposent probablement également des méthodes pour accéder à leur instruction "nombre de zéros", si elles en ont une.

3
BeeOnRope

La solution true la plus rapide:

Un arbre de recherche binaire de 63 entrées. Ce sont les puissances de 2 de 0 à 63. Fonction de génération unique pour créer l’arbre. Les feuilles représentent la base de journal 2 des puissances (en gros, les nombres de 1 à 63).

Pour trouver la réponse, introduisez un nombre dans l'arborescence et naviguez jusqu'au nœud feuille supérieur à l'élément. Si le nœud feuille est exactement égal, résultat est la valeur feuille. Sinon, le résultat est la valeur de feuille + 1.

La complexité est fixée à O (6). 

3
Mahmoud Al-Qudsi

Trouver la base de journal 2 d'un entier (64 bits ou tout autre bit) avec une sortie entière revient à rechercher le bit le plus significatif qui est défini. Pourquoi? Parce que la base de journal 2 correspond au nombre de fois que vous pouvez diviser le nombre par 2 pour atteindre 1. 

Une façon de trouver le MSB défini est simplement de décaler de bits vers la droite de 1 à chaque fois jusqu'à obtenir 0. Une autre méthode plus efficace consiste à effectuer une recherche binaire à l'aide de masques de bit.

La partie ceil est facilement établie en vérifiant si d’autres bits que le MSB sont définis.

2
Brian R. Bondy

Si vous avez des flottants 80 bits ou 128 bits disponibles, convertissez-les en ce type, puis lisez les bits de l'exposant. Ce lien contient des détails (pour les entiers jusqu'à 52 bits) et plusieurs autres méthodes:

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogIEEE64Float

Vérifiez également la source ffmpeg. Je sais qu'ils ont un algorithme très rapide. Même si ce n'est pas directement extensible à des tailles plus grandes, vous pouvez facilement faire quelque chose comme if (x>INT32_MAX) return fastlog2(x>>32)+32; else return fastlog2(x);

1
R..

J'ai référencé plusieurs implémentations d'un "bit le plus élevé" de 64 bits. Le code le plus "sans branche" n'est en fait pas le plus rapide.

Ceci est mon fichier source highest-bit.c:

int highest_bit_unrolled(unsigned long long n)
{
  if (n & 0xFFFFFFFF00000000) {
    if (n & 0xFFFF000000000000) {
      if (n & 0xFF00000000000000) {
        if (n & 0xF000000000000000) {
          if (n & 0xC000000000000000)
            return (n & 0x8000000000000000) ? 64 : 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit_bs(unsigned long long n)
{
  const unsigned long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

int highest_bit_shift(unsigned long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

static int count_ones(unsigned long long d)
{
  d = ((d & 0xAAAAAAAAAAAAAAAA) >>  1) + (d & 0x5555555555555555);
  d = ((d & 0xCCCCCCCCCCCCCCCC) >>  2) + (d & 0x3333333333333333);
  d = ((d & 0xF0F0F0F0F0F0F0F0) >>  4) + (d & 0x0F0F0F0F0F0F0F0F);
  d = ((d & 0xFF00FF00FF00FF00) >>  8) + (d & 0x00FF00FF00FF00FF);
  d = ((d & 0xFFFF0000FFFF0000) >> 16) + (d & 0x0000FFFF0000FFFF);
  d = ((d & 0xFFFFFFFF00000000) >> 32) + (d & 0x00000000FFFFFFFF);
  return d;
}

int highest_bit_parallel(unsigned long long n)
{
  n |= n >> 1;
  n |= n >> 2;
  n |= n >> 4;
  n |= n >> 8;
  n |= n >> 16;
  n |= n >> 32;
  return count_ones(n);
}

int highest_bit_so(unsigned long long x)
{
  static const unsigned long long t[6] = {
    0xFFFFFFFF00000000ull,
    0x00000000FFFF0000ull,
    0x000000000000FF00ull,
    0x00000000000000F0ull,
    0x000000000000000Cull,
    0x0000000000000002ull
  };

  int y = (((x & (x - 1)) == 0) ? 0 : 1);
  int j = 32;
  int i;

  for (i = 0; i < 6; i++) {
    int k = (((x & t[i]) == 0) ? 0 : j);
    y += k;
    x >>= k;
    j >>= 1;
  }

  return y;
}

int highest_bit_so2(unsigned long long value)
{
  int pos = 0;
  if (value & (value - 1ULL))
  {
    pos = 1;
  }
  if (value & 0xFFFFFFFF00000000ULL)
  {
    pos += 32;
    value = value >> 32;
  }
  if (value & 0x00000000FFFF0000ULL)
  {
    pos += 16;
    value = value >> 16;
  }
  if (value & 0x000000000000FF00ULL)
  {
    pos += 8;
    value = value >> 8;
  }
  if (value & 0x00000000000000F0ULL)
  {
    pos += 4;
    value = value >> 4;
  }
  if (value & 0x000000000000000CULL)
  {
    pos += 2;
    value = value >> 2;
  }
  if (value & 0x0000000000000002ULL)
  {
    pos += 1;
    value = value >> 1;
  }
  return pos;
}

Ceci est highest-bit.h:

int highest_bit_unrolled(unsigned long long n);
int highest_bit_bs(unsigned long long n);
int highest_bit_shift(unsigned long long n);
int highest_bit_parallel(unsigned long long n);
int highest_bit_so(unsigned long long n);
int highest_bit_so2(unsigned long long n);

Et le programme principal (désolé pour tout le copier-coller):

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "highest-bit.h"

double timedelta(clock_t start, clock_t end)
{
  return (end - start)*1.0/CLOCKS_PER_SEC;
}

int main(int argc, char **argv)
{
  int i;
  volatile unsigned long long v;
  clock_t start, end;

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_unrolled(v);
  }

  end = clock();

  printf("highest_bit_unrolled = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_parallel(v);
  }

  end = clock();

  printf("highest_bit_parallel = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_bs(v);
  }

  end = clock();

  printf("highest_bit_bs = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_shift(v);
  }

  end = clock();

  printf("highest_bit_shift = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_so(v);
  }

  end = clock();

  printf("highest_bit_so = %6.3fs\n", timedelta(start, end));

  start = clock();

  for (i = 0; i < 10000000; i++) {
    for (v = 0x8000000000000000; v; v >>= 1)
      highest_bit_so2(v);
  }

  end = clock();

  printf("highest_bit_so2 = %6.3fs\n", timedelta(start, end));

  return 0;
}

J'ai essayé ces différents boîtiers Intel x86, anciens et nouveaux.

Le highest_bit_unrolled (recherche binaire déroulée) est toujours nettement plus rapide que highest_bit_parallel (opérations binaires sans branche). Ceci est plus rapide que highest_bit_bs (boucle de recherche binaire) et à son tour est plus rapide que highest_bit_shift (décalage naïf et boucle de comptage).

highest_bit_unrolled est également plus rapide que celui de la réponse acceptée SO (highest_bit_so) et de la réponse donnée dans une autre réponse (highest_bit_so2).

Le benchmark parcourt un masque à un bit qui couvre des bits successifs. C'est pour essayer de déjouer la prédiction de branche dans la recherche binaire déroulée, ce qui est réaliste: dans un programme du monde réel, les cas d'entrées ont peu de chances de montrer la localité de la position du bit.

Voici les résultats sur une ancienne Intel(R) Core(TM)2 Duo CPU E4500 @ 2.20GHz:

$ ./highest-bit
highest_bit_unrolled =  6.090s
highest_bit_parallel =  9.260s
highest_bit_bs = 19.910s
highest_bit_shift = 21.130s
highest_bit_so =  8.230s
highest_bit_so2 =  6.960s

Sur un modèle plus récent Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz:

highest_bit_unrolled =  1.555s
highest_bit_parallel =  3.420s
highest_bit_bs =  6.486s
highest_bit_shift =  9.505s
highest_bit_so =  4.127s
highest_bit_so2 =  1.645s

Sur le nouveau matériel, highest_bit_so2 se rapproche de highest_bit_unrolled sur le nouveau matériel. L'ordre n'est pas tout à fait le même. maintenant, highest_bit_so est vraiment en retard et est plus lent que highest_bit_parallel.

Le plus rapide, highest_bit_unrolled contient le plus de code avec le plus de branches. Chaque valeur de retour atteinte par un ensemble différent de conditions avec son propre morceau de code dédié.

L'intuition de "éviter toutes les branches" (en raison d'inquiétudes au sujet de mauvaises prédictions de branche) n'est pas toujours juste. Les processeurs modernes (et même moins modernes) font preuve d'une ruse considérable pour ne pas être gênés par la ramification.


P.S. le highest_bit_unrolled a été introduit dans le langage TXR dans décembre 2011 (avec des erreurs, depuis le débogage).

Récemment, j'ai commencé à me demander si un code plus agréable, plus compact et sans branches, ne serait pas plus rapide.

Je suis un peu surpris par les résultats.

On peut soutenir que le code devrait être réellement #ifdef- pour GNU C et utiliser certaines primitives du compilateur, mais en ce qui concerne la portabilité, cette version reste.

1
Kaz

La recherche linéaire naïve peut être une option pour les nombres entiers uniformément répartis, car elle nécessite un peu moins de 2 comparaisons en moyenne (pour toute taille entière).

/* between 1 and 64 comparisons, ~2 on average */
#define u64_size(c) (              \
    0x8000000000000000 < (c) ? 64  \
  : 0x4000000000000000 < (c) ? 63  \
  : 0x2000000000000000 < (c) ? 62  \
  : 0x1000000000000000 < (c) ? 61  \
...
  : 0x0000000000000002 < (c) ?  2  \
  : 0x0000000000000001 < (c) ?  1  \
  :                             0  \
)
1
Loic

Le code ci-dessous est plus simple et fonctionnera tant que l'entrée x> = 1. L'entrée clog2 (0) obtiendra une réponse indéfinie (ce qui est logique car log (0) est infini ...) Vous pouvez ajouter une vérification d'erreur pour ( x == 0) si vous voulez:

unsigned int clog2 (unsigned int x)
{
    unsigned int result = 0;
    --x;
    while (x > 0) {
        ++result;
        x >>= 1;
    }

    return result;
}

Soit dit en passant, le code du plancher de log2 est similaire: (Encore une fois, en supposant que x> = 1)

unsigned int flog2 (unsigned int x)
{
    unsigned int result = 0;
    while (x > 1) {
        ++result;
        x >>= 1;
    }

    return result;
}
0
user3225538

Je vais vous donner le moyen le plus rapide pour x86-64 au moment de l'écriture, et une technique générale si vous avez un plancher rapide qui fonctionne pour les arguments <2 ^ 63 , si vous vous souciez de la plage complète, puis voir ci-dessous.

Je suis surpris de la qualité médiocre des autres réponses car elles vous expliquent comment obtenir le sol mais le transforment de manière très onéreuse (avec des conditionnelles et tout!) Au plafond.

Si vous pouvez obtenir rapidement l’étage du logarithme, par exemple en utilisant __builtin_clzll, l’obtention de l’étage se fait très facilement de la manière suivante:

unsigned long long log2Floor(unsigned long long x) {
    return 63 - __builtin_clzll(x);
}

unsigned long long log2Ceiling(unsigned long long x) {
    return log2Floor(2*x - 1);
}

Cela fonctionne car il ajoute 1 au résultat sauf si x est exactement une puissance de 2.

Voir la différence d'assembleur x86-64 à l'explorateur du compilateur pour une autre implémentation de plafond comme celle-ci:

auto log2CeilingDumb(unsigned long x) {
    return log2Floor(x) + (!!(x & (x - 1)));
}

Donne:

log2Floor(unsigned long): # @log2Floor(unsigned long)
  bsr rax, rdi
  ret
log2CeilingDumb(unsigned long): # @log2CeilingDumb(unsigned long)
  bsr rax, rdi
  lea rcx, [rdi - 1]
  and rcx, rdi
  cmp rcx, 1
  sbb eax, -1
  ret
log2Ceiling(unsigned long): # @log2Ceiling(unsigned long)
  lea rax, [rdi + rdi]
  add rax, -1
  bsr rax, rax
  ret

Pour la gamme complète, il est dans une réponse précédente: return log2Floor(x - 1) + 1, ceci est nettement plus lent car il utilise en x86-64 quatre instructions par rapport aux trois précédentes.

0
TheCppZoo