web-dev-qa-db-fra.com

Quel est le moyen le plus rapide/le plus efficace de trouver le bit de réglage le plus élevé (msb) dans un entier en C?

Si j'ai un entier n et que je veux connaître la position du bit le plus significatif (c’est-à-dire que si le bit le moins significatif est à droite, je veux connaître la position du bit le plus à gauche qui est un 1), Quelle est la méthode la plus rapide/la plus efficace pour découvrir?

Je sais que POSIX prend en charge une méthode ffs() dans strings.h pour trouver le premier bit défini, mais il ne semble pas exister de méthode fls() correspondante.

Y at-il un moyen vraiment évident de faire cela qui me manque?

Qu'en est-il des cas où vous ne pouvez pas utiliser les fonctions POSIX pour la portabilité?

Edit: Qu'en est-il d'une solution qui fonctionne à la fois sur les architectures 32 et 64 bits (beaucoup de listes de codes semblent ne fonctionner que sur des ints 32 bits).

102
Zxaos

GCC a :

  - Fonction intégrée: int __builtin_clz (unsigned int x) 
 Retourne le nombre de bits 0 en avant dans X, en commençant au plus 
 position de bit significative. Si X est 0, le résultat est indéfini .

 - Fonction intégrée: int __builtin_clzl (unsigned long) 
 Similaire à __builtin_clz ', sauf que le type d'argument est `unsigned 
 longue'.

 - Fonction intégrée: int __builtin_clzll (unsigned long long) 
 Similaire à __builtin_clz ', sauf que le type d'argument est `unsigned 
 long long '.

Je m'attendrais à ce qu'ils soient traduits en quelque chose de raisonnablement efficace pour votre plate-forme actuelle, qu'il s'agisse de l'un de ces algorithmes sophistiqués, ou d'une seule instruction.


Un truc utile si votre entrée peut être zéro, c'est __builtin_clz(x | 1): définir inconditionnellement le bit le plus bas sans modifier les autres, rend la sortie 0 pour x=0, sans modifier la sortie pour une autre entrée.

Pour éviter de devoir le faire, votre autre option est des éléments intrinsèques spécifiques à la plate-forme, tels que le __clz de GCC ARM GCC, ou le _lzcnt_u32 de x86 sur les CPU prenant en charge l'instruction lzcnt. (Attention, lzcnt décode en bsr sur les CPU plus anciens au lieu de défaut, ce qui donne 31-lzcnt pour des entrées non nulles.)

Il n’existe malheureusement aucun moyen de profiter de manière portable des différentes instructions CLZ sur des plates-formes autres que x86 qui définissent le résultat pour input = 0 à 32 ou 64 (en fonction de la largeur de l’opérande). lzcnt de x86 le fait aussi, alors que bsr produit un index binaire que le compilateur doit retourner, sauf si vous utilisez 31-__builtin_clz(x).

(Le "résultat indéfini" n'est pas un comportement indéfini en C, mais une valeur non définie. C'est ce qui se trouvait dans le registre de destination lorsque l'instruction a été exécutée. AMD documente cela, pas Intel, mais les processeurs d'Intel implémentent ce comportement. Mais c’est pas ce qui était auparavant dans la variable C que vous assignez, ce n’est généralement pas le cas lorsque gcc transforme C en asm. Voir aussi Pourquoi ne pas rompre la "dépendance de sortie" de LZCNT matière? )

53
ephemient

En supposant que vous soyez sur x86 et que vous jouiez pour un assembleur intégré, Intel fournit une instruction BSR ("bit scan reverse"). C'est rapide sur certains x86 (microcodés sur d'autres). Du manuel:

Recherche dans l'opérande source l'ensemble le plus significatif bit (1 bit). Si un plus significatif 1 bit est trouvé, son index est stocké dans l'opérande de destination. L'opérande source peut être un enregistrer ou un emplacement de mémoire; la l’opérande de destination est un registre. Le index de bits est un décalage non signé de bit 0 de l'opérande source. Si la l'opérande source de contenu est 0, le le contenu de l'opérande de destination est indéfini.

(Si vous êtes sur PowerPC, il existe une instruction similaire cntlz ("compter les zéros").)

Exemple de code pour gcc:

#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" : "=r"(msb) : "r"(n));
    std::cout << n << " : " << msb << std::endl;
  }
  return 0;
}

Voir aussi ce tutoriel en ligne sur l'assembleur , qui montre (section 9.4) qu'il est considérablement plus rapide que le code en boucle.

40
timday

Etant donné que 2 ^ N est un entier dont seul le Nème bit est défini (1 << N), la position (N) du bit d’établissement le plus élevé est l’entier log base 2 de cet entier.

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

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

Cet algorithme "évident" peut ne pas être transparent pour tout le monde, mais lorsque vous réalisez que le code est décalé d'un bit vers la droite jusqu'à ce que le bit le plus à gauche soit désactivé (notez que C traite toute valeur non nulle comme vraie) et renvoie le nombre des changements, c'est parfaitement logique. Cela signifie également que cela fonctionne même lorsque plus d'un bit est défini - le résultat est toujours pour le bit le plus significatif.

Si vous faites défiler cette page, il existe des variantes plus rapides et plus complexes. Cependant, si vous savez que vous avez affaire à des nombres avec beaucoup de zéros, l'approche naïve peut fournir une vitesse acceptable, car le décalage des bits est assez rapide en C, et le simple algorithme n'exige pas l'indexation d'un tableau.

NOTE: Lorsque vous utilisez des valeurs 64 bits, soyez extrêmement prudent avec l'utilisation d'algorithmes très astucieux. beaucoup d'entre eux ne fonctionnent correctement que pour les valeurs 32 bits.

35
Quinn Taylor

Cela devrait être rapide comme l'éclair:

int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}
15
Protagonist

C'est un peu comme trouver une sorte de journal entier. Il y a des trucs très difficiles, mais j'ai créé mon propre outil pour cela. L’objectif est bien sûr la vitesse. 

Je me rends compte que le processeur a déjà un détecteur de bits automatique, utilisé pour la conversion de nombres entiers en nombres flottants! Alors utilisez ça.

double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness

Cette version convertit la valeur en un double, puis lit l'exposant, qui vous indique où se trouvait le bit. Le décalage et soustraction de fantaisie consiste à extraire les pièces appropriées de la valeur IEEE.

Il est légèrement plus rapide d’utiliser des flottants, mais un flotteur ne peut vous donner que les 24 premières positions en raison de sa précision moindre.


Pour le faire en toute sécurité, sans comportement indéfini en C++ ou C, utilisez memcpy au lieu de la conversion du pointeur pour le typage. Les compilateurs savent comment l’aligner efficacement.

// static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

Ou, à partir de C99, utilisez un union {double d; uint32_t u[2];};. Cependant, notez qu'en C++, la punition de type union n'est prise en charge que par certains compilateurs en tant qu'extension, et non par ISO C++.


Cela sera généralement plus lent qu'un élément intrinsèque spécifique à la plate-forme pour une instruction de comptage de zéros, mais ISO C portable n'a pas cette fonction. Certains processeurs manquent également d'une instruction de comptage avec zéro, mais certains peuvent convertir efficacement des entiers en double. La frappe de type d'un modèle de bit FP vers un nombre entier peut être lente, par exemple (par exemple, sur PowerPC, elle nécessite un enregistrement/un rechargement et provoque généralement un blocage load-store-store).

Cet algorithme pourrait être potentiellement utile pour les implémentations SIMD, car moins de processeurs ont SIMD lzcnt. x86 a seulement reçu une telle instruction avec AVX512CD

12
SPWorley

Kaz Kylheku ici 

J'ai comparé deux approches pour cela sur des nombres de 63 bits (le type long long sur gcc x86_64), en évitant le bit de signe.

(Il se trouve que j'ai besoin de ce "trouver le bit le plus élevé" pour quelque chose, vous voyez.)

J'ai implémenté la recherche binaire pilotée par les données (étroitement basée sur l'une des réponses ci-dessus). J'ai également implémenté manuellement un arbre de décision complètement déroulé, qui consiste simplement en un code avec des opérandes immédiats. Pas de boucles, pas de tables.

L'arbre de décision (montant le plus élevé) a été évalué comme étant 69% plus rapide, sauf dans le cas n = 0 pour lequel la recherche binaire comporte un test explicite.

Le test spécial de la recherche binaire pour 0 cas est seulement 48% plus rapide que l'arbre de décision, qui ne comporte pas de test spécial.

Compilateur, machine: (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5).

int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 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(long long n)
{
  const 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;
}

Programme de test rapide et sale:

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

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d\n", n, n, b1, b2);

  printf("time1 = %d\n", (int) (mid - start));
  printf("time2 = %d\n", (int) (end - mid));
  return 0;
}

En utilisant uniquement -O2, la différence devient plus grande. L'arbre de décision est presque quatre fois plus rapide.

J'ai également comparé le code naïf de décalage de bits:

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

Ce n'est rapide que pour les petits nombres, comme on pourrait s'y attendre. Pour déterminer que le bit le plus élevé est 1 pour n == 1, il a été analysé plus rapidement que 80%. Cependant, la moitié des nombres choisis au hasard dans l'espace de 63 bits ont le 63ème bit défini!

Sur l'entrée 0x3FFFFFFFFFFFFFFFFF, la version de l'arbre de décision est un peu plus rapide que sur 1 et indique une vitesse supérieure de 1120% (12,2 fois) à celle du décaleur de bits.

Je vais également comparer l’arbre de décision aux fonctions intégrées de GCC et essayer un mélange d’entrées plutôt que de répéter le même nombre. Il se peut que certaines prédictions de branche se collent et peut-être des scénarios de mise en cache irréalistes qui accélèrent artificiellement les répétitions.

9
Kaz

Qu'en est-il de

int highest_bit(unsigned int a) {
    int count;
    std::frexp(a, &count);
    return count - 1;
}

?

7
Marco Amagliani
unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

1 registre, 13 instructions. Croyez-le ou non, cela est généralement plus rapide que l'instruction BSR mentionnée ci-dessus, qui fonctionne en temps linéaire. C'est le temps logarithmique.

De http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit

6
rlbond

Voici quelques points de repère (simples), d'algorithmes actuellement donnés sur cette page ...

Les algorithmes n'ont pas été testés sur toutes les entrées de unsigned int; alors vérifiez d'abord, avant d'utiliser aveuglément quelque chose;)

Sur ma machine, clz (__builtin_clz) et asm fonctionnent mieux. ASM semble encore plus rapide que CLZ ... mais cela pourrait être dû à la simple référence ...

//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  

#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)



/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)


/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)




/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)




/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



/***************** bitshift2 ********************/
int POS[32] = {0, 1, 28, 2, 29, 14, 24, 3,
             30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
             16, 7, 26, 12, 18, 6, 11, 5, 10, 9};

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])

#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)



#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math\n");
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));

  printf("\n\n");

  printf("clz\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  printf("\n\n");

  printf("i2f\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("\n\n");

  printf("asm\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("\n\n");

  printf("bitshift1\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("\n\n");

  printf("bitshift2\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("\n\nPlease wait...\n\n");


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");

  return EXIT_SUCCESS;
}
5
Josh

Bien que je n’utiliserais probablement cette méthode que si j’avais absolument besoin des meilleures performances possibles (par exemple, pour écrire une sorte d’IA de jeu de société impliquant des bitboards), la solution la plus efficace consiste à utiliser l’ASM en ligne. Voir la section Optimisations de cet article de blog pour le code avec une explication.

[...], l'instruction bsrl Assembly calcule la position du bit le plus significatif. Ainsi, nous pourrions utiliser cette déclaration asm:

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));
5
Noldorin

J'avais besoin d'une routine pour le faire et avant de chercher sur le Web (et de trouver cette page), je suis venu avec ma propre solution basée sur une recherche binaire. Bien que je suis sûr que quelqu'un a déjà fait ça! Il fonctionne en temps constant et peut être plus rapide que la solution "évidente" affichée, bien que je ne fasse pas de grandes affirmations, je les publie simplement par intérêt.

int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}
4
dangermouse

Certaines réponses trop complexes ici. La technique Debruin ne doit être utilisée que lorsque l’entrée est déjà une puissance de deux, sinon il existe un meilleur moyen. Pour une puissance de 2 entrées, Debruin est le plus rapide absolu, même plus rapide que _BitScanReverse sur tous les processeurs que j'ai testés. Cependant, dans le cas général, _BitScanReverse (ou quel que soit le nom intrinsèque appelé dans votre compilateur) est le plus rapide (sur certains CPU, il peut cependant être microcodé).

Si la fonction intrinsèque n’est pas une option, voici une solution logicielle optimale pour le traitement des entrées générales.

u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

Notez que cette version ne nécessite pas de recherche Debruin à la fin, contrairement à la plupart des autres réponses. Il calcule la position en place.

Les tables peuvent être préférables, cependant, si vous l'appelez plusieurs fois de suite, le risque d'échec de cache devient éclipsé par l'accélération d'une table.

u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

Cela devrait produire le débit le plus élevé de toutes les réponses logicielles données ici, mais si vous ne l'appelez que de temps en temps, préférez une solution sans table comme mon premier extrait.

4
VoidStar

c'est une sorte de recherche binaire, cela fonctionne avec toutes sortes de types entiers (non signés!)

#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

à compléter:

#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}
3
Benjamin Endulat

Comme le soulignent les réponses ci-dessus, il existe plusieurs façons de déterminer le bit le plus significatif. Cependant, comme cela a également été souligné, les méthodes seront probablement uniques aux registres 32 bits ou 64 bits. La page bithacks stanford.edu fournit des solutions qui fonctionnent aussi bien en informatique 32 bits que 64 bits. Avec un peu de travail, ils peuvent être combinés pour fournir une approche multi-architectures solide permettant d’obtenir le MSB. La solution à laquelle je suis arrivé qui compilait/travaillait sur des ordinateurs 64 & 32 bits était:

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/* 
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */
int
getmsb (uint32_t Word)
{
    int r = 0;
    if (Word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_Word_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_Word_ORDER!=LITTLE_ENDIAN] = Word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_Word_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (Word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}
3
David C. Rankin

Une version en C utilisant des approximations successives:

unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

Avantage: le temps d'exécution est constant quel que soit le nombre fourni, car le nombre de boucles est toujours le même . (4 boucles avec "unsigned int")

3
user3177100

c99 nous a donné log2 . Cela supprime le besoin de toutes les implémentations spéciales de sauce log2 que vous voyez sur cette page. Vous pouvez utiliser l'implémentation log2 du standard comme ceci:

const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

Une variable n de 0UL doit également être protégée, car:

-∞ est retourné et FE_DIVBYZERO est levé

J'ai écrit un exemple avec cette vérification qui définit arbitrairement Index sur ULONG_MAX ici: https://ideone.com/u26vsi


Le visual-studio corollaire à la seule réponse gcc de l'ephemient est:

const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

La documentation de _BitScanReverse indique que Index est:

Chargé avec la position de bit du premier bit défini (1) trouvé

En pratique, j'ai constaté que si n est 0UL, alors Index EST D&EACUTE;FINI SUR 0UL , exactement comme pour une n de 1UL. Mais la seule chose garantie dans la documentation dans le cas d'une n de 0UL est que le retour est:

0 si aucun bit défini n'a été trouvé

Ainsi, de la même manière que l'implémentation préférable log2 au-dessus du retour doit être vérifiée, définissez Index sur une valeur marquée dans ce cas. J'ai de nouveau écrit un exemple d'utilisation de ULONG_MAX pour cette valeur d'indicateur ici: http://rextester.com/GCU61409

3
Jonathan Mee

Pensez opérateurs bitwise.

J'ai mal compris la question la première fois. Vous devriez produire un int avec le bit le plus à gauche (les autres nuls). En supposant que cmp est défini sur cette valeur:

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}
2
Vasil

En développant le repère de Josh, on peut améliorer la clz comme suit

/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

En ce qui concerne asm: notez qu'il existe bsr et bsrl (c'est la version "longue") la normale pourrait être un peu plus rapide.

2
JonesD

Compte tenu de ce qu’il s’agit d’une approche «encore une autre», il semble être différent des autres déjà données.

renvoie -1 si x==0, sinon floor( log2(x)) (résultat maximal 31)

Réduisez le problème de 32 à 4 bits, puis utilisez un tableau. Peut-être inélégant, mais pragmatique.

C’est ce que j’utilise lorsque je ne souhaite pas utiliser __builtin_clz en raison de problèmes de portabilité.

Pour le rendre plus compact, vous pouvez utiliser une boucle pour réduire, en ajoutant 4 à r à chaque fois, 7 itérations au maximum. Ou certains hybrides, tels que (pour 64 bits): boucle à réduire à 8, test à réduire à 4.

int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}
1
greggo

Mon humble méthode est très simple:

MSB (x) = INT [Journal (x)/Journal (2)]

Traduction: le MSB de x est la valeur entière de (journal de base x divisé par le journal de base 2).

Cela peut être facilement et rapidement adapté à n’importe quel langage de programmation. Essayez-le sur votre calculatrice pour voir par vous-même que cela fonctionne.

1
SpartanWar

Woaw, c'était beaucoup de réponses. Je ne suis pas désolé d'avoir répondu à une vieille question.

int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

Cette réponse est assez semblable à une autre réponse ... oh bien. 

1
Harry Svensson

Notez que ce que vous essayez de faire est de calculer l’entier log2 d’un entier,

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

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

Observez que vous pouvez essayer de rechercher plus d'un bit à la fois.

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

Cette approche utilise une recherche binaire

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

Une autre méthode de recherche binaire, peut-être plus lisible,

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

Et parce que vous voudrez les tester,

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}
1
ChuckCottrill

Je suppose que votre question concerne un entier (appelé v ci-dessous) et non un entier non signé.

int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x8000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

Si vous voulez que cela fonctionne sans prendre en compte le signe, vous pouvez ajouter un 'v << = 1;' supplémentaire avant la boucle (et changez la valeur de r en 30 en conséquence) . S'il vous plaît laissez-moi savoir si j'ai oublié quelque chose. Je ne l'ai pas testé mais ça devrait marcher.

0
Antonin GAVREL

Le code:

    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf( "The left-most non zero bit of %d is bit %d\n", x, p);
    }

Ou obtenez la partie entière de l'instruction FPU FYL2X (Y * Log2 X) en définissant Y = 1

0
jemin