web-dev-qa-db-fra.com

Rechercher le bit le plus significatif (le plus à gauche) défini dans un tableau de bits

J'ai une implémentation de tableau de bits où le 0ème index est le MSB du premier octet dans un tableau, le 8ème index est le MSB du deuxième octet, etc ...

Comment trouver rapidement le premier bit défini dans ce tableau de bits? Toutes les solutions connexes que j'ai recherchées trouvent le premier bit le moins significatif, mais j'ai besoin du premier le plus significatif. Donc, étant donné 0x00A1, je veux 8 (car c'est le 9e bit de gauche).

38
Claudiu

GCC a __builtin_clz qui se traduit par BSR sur x86/x64, CLZ sur ARM, etc. et émule l'instruction si le matériel ne l'implémente pas.
Visual C++ 2005 et plus a _BitScanReverse .

39
Andras Vass

tl: dr; Pour 32 bits, utilisez multiplication de Bruijn .

C'est l'algorithme portable "" le plus rapide " . Il est sensiblement plus rapide et plus correct que tous les autres algorithmes MSB 32 bits portables de ce thread.

L'algorithme de Bruijn renvoie également un résultat correct lorsque l'entrée est nulle. Les instructions __builtin_clz et _BitScanReverse retournent des résultats incorrects lorsque l'entrée est nulle.

Sous Windows x86-64, la multiplication de Bruijn s'exécute à une vitesse comparable à la fonction équivalente (imparfaite) de Windows, avec une différence de performances d'environ 3% seulement.

Voici le code.

u32 msbDeBruijn32( u32 v )
{
    static const int MultiplyDeBruijnBitPosition[32] =
    {
        0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
        8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
    };

    v |= v >> 1; // first round down to one less than a power of 2
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;

    return MultiplyDeBruijnBitPosition[( u32 )( v * 0x07C4ACDDU ) >> 27];
}

Toutes les autres réponses de ce fil fonctionnent soit beaucoup plus mal que leurs auteurs ne le suggèrent, ou ne calculent pas le résultat correctement, ou les deux. Essayons de les comparer tous et vérifions qu'ils font ce qu'ils prétendent faire.

Voici un simple faisceau C++ 11 pour tester toutes ces implémentations. Il compile proprement sur Visual Studio mais devrait fonctionner sur tous les compilateurs modernes. Il vous permet d'exécuter le test en mode performance (bVerifyResults = false) et en mode vérification (bVerifyResults = true).

Voici les résultats en mode vérification:

Verification failed for msbNative64: input was 0; output was 818af060; expected 0
Verification failed for msbFfs: input was 22df; output was 0; expected d
Verification failed for msbPerformanceJunkie32: input was 0; output was ffffffff; expected 0
Verification failed for msbNative32: input was 0; output was 9ab07060; expected 0

Le "junkie de performance" et les implémentations natives de Microsoft font des choses différentes lorsque l'entrée est nulle. msbPerformanceJunkie32 produit -1 et _BitScanReverse de Microsoft produit un nombre aléatoire, cohérent avec l'instruction matérielle sous-jacente. L'implémentation msbPerformanceJunkie32 produit également un résultat qui est désactivé par l'une de toutes les autres réponses.

Voici les résultats en mode performance, fonctionnant sur mon ordinateur portable i7-4600, compilés en mode release:

msbLoop64 took 2.56751 seconds               
msbNative64 took 0.222197 seconds            

msbLoop32 took 1.43456 seconds               
msbFfs took 0.525097 seconds                 
msbPerformanceJunkie32 took 1.07939 seconds  
msbDeBruijn32 took 0.224947 seconds          
msbNative32 took 0.218275 seconds            

La version de Bruijn bat les autres implémentations sainement car elle est sans branche, et donc elle fonctionne bien avec des entrées qui produisent un ensemble de sorties uniformément réparties. Toutes les autres versions sont plus lentes contre les entrées arbitraires en raison des pénalités de mauvaise prédiction de branche sur les processeurs modernes. La fonction smbFfs produit des résultats incorrects et peut donc être ignorée.

Certaines implémentations fonctionnent sur des entrées 32 bits et d'autres sur des entrées 64 bits. Un modèle nous aidera à comparer les pommes aux pommes, quelle que soit la taille d'entrée.

Voici le code. Téléchargez et exécutez les benchmarks vous-même si vous le souhaitez.

#include <iostream>
#include <chrono>
#include <random>
#include <cassert>
#include <string>
#include <limits>

#ifdef _MSC_VER
#define Microsoft_COMPILER 1
#include <intrin.h>
#endif // _MSC_VER

const int iterations = 100000000;
bool bVerifyResults = false;
std::random_device rd;
std::default_random_engine re(rd());
typedef unsigned int u32;
typedef unsigned long long u64;

class Timer
{
public:
    Timer() : beg_(clock_::now()) {}
    void reset() {
        beg_ = clock_::now();
    }
    double elapsed() const {
        return std::chrono::duration_cast<second_>
            (clock_::now() - beg_).count();
    }

private:
    typedef std::chrono::high_resolution_clock clock_;
    typedef std::chrono::duration<double, std::ratio<1> > second_;
    std::chrono::time_point<clock_> beg_;
};

unsigned int msbPerformanceJunkie32(u32 x)
{
    static const unsigned int bval[] =
    { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 };
    unsigned int r = 0;
    if (x & 0xFFFF0000) {
        r += 16 / 1;
        x >>= 16 / 1;
    }
    if (x & 0x0000FF00) {
        r += 16 / 2;
        x >>= 16 / 2;
    }
    if (x & 0x000000F0) {
        r += 16 / 4;
        x >>= 16 / 4;
    }
    return r + bval[x];
}

#define FFS(t)  \
{ \
register int n = 0; \
if (!(0xffff & t)) \
n += 16; \
if (!((0xff << n) & t)) \
n += 8; \
if (!((0xf << n) & t)) \
n += 4; \
if (!((0x3 << n) & t)) \
n += 2; \
if (!((0x1 << n) & t)) \
n += 1; \
return n; \
}

unsigned int msbFfs32(u32 x)
{
    FFS(x);
}

unsigned int msbLoop32(u32 x)
{
    int r = 0;
    if (x < 1) return 0;
    while (x >>= 1) r++;
    return r;
}

unsigned int msbLoop64(u64 x)
{
    int r = 0;
    if (x < 1) return 0;
    while (x >>= 1) r++;
    return r;
}

u32 msbDeBruijn32(u32 v)
{
    static const int MultiplyDeBruijnBitPosition[32] =
    {
        0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
        8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
    };

    v |= v >> 1; // first round down to one less than a power of 2
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;

    return MultiplyDeBruijnBitPosition[(u32)(v * 0x07C4ACDDU) >> 27];
}

#ifdef Microsoft_COMPILER
u32 msbNative32(u32 val)
{
    unsigned long result;
    _BitScanReverse(&result, val);
    return result;
}
u32 msbNative64(u64 val)
{
    unsigned long result;
    _BitScanReverse64(&result, val);
    return result;
}
#endif // Microsoft_COMPILER

template <typename InputType>
void test(unsigned int msbFunc(InputType),
    const std::string &name,
    const std::vector< InputType > &inputs,
    std::vector< unsigned int > &results,
    bool bIsReference = false
)
{
    if (bIsReference)
    {
        int i = 0;
        for (int i = 0; i < iterations; i++)
            results[i] = msbFunc(inputs[i]);
    }
    InputType result;
    if (bVerifyResults)
    {
        bool bNotified = false;
        for (int i = 0; i < iterations; i++)
        {
            result = msbFunc(inputs[i]);
            if ((result != results[i]) && !bNotified)
            {
                std::cout << "Verification failed for " << name << ": "
                    << "input was " << std::hex << inputs[i]
                    << "; output was " << result
                    << "; expected " << results[i]
                    << std::endl;
                bNotified = true;
            }
        }
    }
    else
    {
        Timer t;
        for (int i = 0; i < iterations; i++)
        {
            result = msbFunc(inputs[i]);
        }
        double elapsed = t.elapsed();
        if ( !bIsReference )
            std::cout << name << " took " << elapsed << " seconds" << std::endl;
        if (result == -1.0f)
            std::cout << "this comparison only exists to keep the compiler from " <<
            "optimizing out the benchmark; this branch will never be called";
    }
}

void main()
{
    std::uniform_int_distribution <u64> dist64(0,
        std::numeric_limits< u64 >::max());
    std::uniform_int_distribution <u32> shift64(0, 63);
    std::vector< u64 > inputs64;
    for (int i = 0; i < iterations; i++)
    {
        inputs64.Push_back(dist64(re) >> shift64(re));
    }
    std::vector< u32 > results64;
    results64.resize(iterations);

    test< u64 >(msbLoop64, "msbLoop64", inputs64, results64, true);
    test< u64 >(msbLoop64, "msbLoop64", inputs64, results64, false);
#ifdef Microsoft_COMPILER
    test< u64 >(msbNative64, "msbNative64", inputs64, results64, false);
#endif // Microsoft_COMPILER
    std::cout << std::endl;

    std::uniform_int_distribution <u32> dist32(0,
        std::numeric_limits< u32 >::max());
    std::uniform_int_distribution <u32> shift32(0, 31);
    std::vector< u32 > inputs32;
    for (int i = 0; i < iterations; i++)
        inputs32.Push_back(dist32(re) >> shift32(re));
    std::vector< u32 > results32;
    results32.resize(iterations);


    test< u32 >(msbLoop32, "msbLoop32", inputs32, results32, true);

    test< u32 >(msbLoop32, "msbLoop32", inputs32, results32, false);
    test< u32 >(msbFfs32, "msbFfs", inputs32, results32, false);
    test< u32 >(msbPerformanceJunkie32, "msbPerformanceJunkie32",
        inputs32, results32, false);
    test< u32 >(msbDeBruijn32, "msbDeBruijn32", inputs32, results32, false);
#ifdef Microsoft_COMPILER
    test< u32 >(msbNative32, "msbNative32", inputs32, results32, false);
#endif // Microsoft_COMPILER
}
23
johnwbyrd

En tant que junkie de la performance, j'ai essayé une tonne de variations pour l'ensemble MSB, ce qui suit est le plus rapide que j'ai rencontré,

unsigned int msb32(unsigned int x)
{
    static const unsigned int bval[] =
    {0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4};

    unsigned int r = 0;
    if (x & 0xFFFF0000) { r += 16/1; x >>= 16/1; }
    if (x & 0x0000FF00) { r += 16/2; x >>= 16/2; }
    if (x & 0x000000F0) { r += 16/4; x >>= 16/4; }
    return r + bval[x];
}
19
Sir Slick

Il existe plusieurs façons de le faire, et les performances relatives des différentes implémentations dépendent quelque peu de la machine (il se trouve que je l'ai comparé dans une certaine mesure pour un objectif similaire). Sur certaines machines, il y a même une instruction intégrée pour cela (utilisez-en une si disponible et la portabilité peut être traitée).

Découvrez quelques implémentations ici (sous "base de journaux entiers 2"). Si vous utilisez GCC, consultez les fonctions __builtin_clz et __builtin_clzl (qui le font respectivement pour les entiers non signés et les longs non signés différents de zéro). Le "clz" signifie "compter les zéros non significatifs", qui est encore une autre façon de décrire le même problème.

Bien sûr, si votre tableau de bits ne tient pas dans un mot de machine approprié, vous devez parcourir les mots du tableau pour trouver le premier mot non nul, puis effectuer ce calcul uniquement sur ce mot.

12
Arkku

Recherchez l'instruction asm BSR (Bit scan reverse) x86 pour le moyen le plus rapide de le faire. Du doc ​​d'Intel: Searches the source operand (second operand) for the most significant set bit (1 bit). If a most significant 1 bit is found, its bit index is stored in the destination operand (first operand).

5
ggiroux
3
Martin Beckett

Si vous utilisez x86, vous pouvez battre pratiquement n'importe quelle solution octet par octet ou mot par mot en utilisant les opérations SSE2, combinées aux instructions find-first-bit, qui (dans le monde gcc) sont prononcées "ffs "pour le bit le plus bas et" fls "pour le bit le plus élevé. Pardonnez-moi d'avoir des problèmes (! @ # $% ^) À formater le code "C" dans une réponse; consultez: http://mischasan.wordpress.com/2011/11/03/sse2-bit-trick-ffsfls-for-xmm-registers/

2
Mischa

J'ai travaillé avec un certain nombre de fonctions pour obtenir le bit le plus significatif, mais des problèmes surviennent généralement entre les nombres 32 et 64 bits ou entre les cases x86_64 et x86. Les fonctions __builtin_clz, __builtin_clzl et __builtin_clzll fonctionne bien pour les nombres 32/64 bits et sur les machines x86_64 et x86. Cependant, trois fonctions sont requises. J'ai trouvé un MSB simple qui s'appuie sur le décalage à droite qui gérera tous les cas pour les nombres positifs. Au moins pour l'usage que j'en fais, il a réussi là où d'autres ont échoué:

int
getmsb (unsigned long long x)
{
    int r = 0;
    if (x < 1) return 0;
    while (x >>= 1) r++;
    return r;
}

En désignant l'entrée comme unsigned long long il peut gérer toutes les classes de nombres de unsigned char à unsigned long long et étant donné la définition standard, il est compatible avec les versions x86_64 et x86. Le cas pour 0 est défini pour renvoyer 0, mais peut être modifié selon les besoins. Un test et une sortie simples sont:

int
main (int argc, char *argv[]) {

    unsigned char c0 = 0;
    unsigned char c = 216;
    unsigned short s = 1021;
    unsigned int ui = 32768;
    unsigned long ul = 3297381253;
    unsigned long long ull = 323543844043;

    int i = 32767;

    printf ("  %16u  MSB : %d\n", c0, getmsb (c0));
    printf ("  %16u  MSB : %d\n", c, getmsb (c));
    printf ("  %16u  MSB : %d\n", s, getmsb (s));
    printf ("  %16u  MSB : %d\n", i, getmsb (i));
    printf ("  %16u  MSB : %d\n", ui, getmsb (ui));
    printf ("  %16lu  MSB : %d\n", ul, getmsb (ul));
    printf ("  %16llu  MSB : %d\n", ull, getmsb (ull));

    return 0;
}

Production:

             0  MSB : 0
           216  MSB : 7
          1021  MSB : 9
         32767  MSB : 14
         32768  MSB : 15
    3297381253  MSB : 31
  323543844043  MSB : 38

REMARQUE: pour des considérations de vitesse, utiliser une seule fonction pour accomplir la même chose centrée autour de __builtin_clzll est encore plus rapide d'un facteur d'environ 6.

2
David C. Rankin

J'en ajouterai un!

typedef unsigned long long u64;
typedef unsigned int       u32;
typedef unsigned char      u8;


u8 findMostSignificantBit (u64 u64Val)
{
  u8 u8Shift;
  u8 u8Bit = 0;

  assert (u64Val != 0ULL);

  for (u8Shift = 32 ; u8Shift != 0 ; u8Shift >>= 1)
  {
    u64 u64Temp = u64Val >> u8Shift;
    if (u64Temp)
    {
      u8Bit |= u8Shift; // notice not using +=
      u64Val = u64Temp;
    }
  }

  return u8Bit;
}

Bien sûr, cela fonctionne sur un nombre 64 bits (non signé long long), et non sur un tableau. En outre, de nombreuses personnes ont signalé des fonctions g ++ intégrées que je ne connaissais pas. Comme c'est intéressant.

Quoi qu'il en soit, cela trouve le bit le plus significatif en 6 itérations et donne une affirmation si vous avez passé 0 à la fonction. Pas la meilleure fonction à utiliser si vous avez accès à une instruction du chipset.

J'utilise aussi | = au lieu de + = parce que ce sont toujours des puissances de deux, et OR est (classiquement) plus rapide que l'addition. Puisque j'ajoute seulement des puissances uniques de 2 ensemble, Je ne me suis jamais retourné.

Il s'agit d'une recherche binaire ce qui signifie qu'elle trouve toujours le résultat en 6 itérations.

Encore une fois, c'est mieux:

u8 findMostSignificantBit2 (u64 u64Val)
{
  assert (u64Val != 0ULL);

  return (u8) (__builtin_ctzll(u64Val));
}
1
Richard wicks

Pas le plus rapide, mais ça marche ...

//// C program
#include <math.h>

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

#define NUM_OF_HIGHESTBIT(a) ((!(a))          \
        ? 0 /* no msb set*/                   \
        : (1 << POS_OF_HIGHESTBIT(a) ))
// could be changed and optimized, if it is known that the following NEVER holds: a <= 0



int main()
{
  unsigned a = 5; // 0b101
  unsigned b = NUM_OF_HIGHESTBIT(a); // 4 since 4 = 0b100
  return 0; 
}
1
Jeff

x86 a une instruction BSR qui retourne un index binaire (plutôt que le nombre de zéros en tête ci-dessus il).

Mais malheureusement, il n'y a pas intrinsèque portable qui efficacement ​​l'expose pour tous les compilateurs. GNU C fournit __builtin_clz, Mais unsigned bitidx = 31 - __builtin_clz(x); n'optimise pas uniquement BSR avec GCC et ICC actuels (il le fait avec clang, ce qui prouve que l'expression est équivalente donc pourrait).


Ce qui suit définit les macros ou les fonctions BSR32() et BSR64() qui se compilent efficacement pour juste une instruction bsr sur x86. (Produire un résultat incorrect si l'entrée était nulle. Il n'y a aucun moyen avec les intrinsèques de tirer parti du comportement de l'instruction asm de laisser la destination non modifiée pour l'entrée = 0.)

La portabilité vers des non-x86 nécessiterait un supplément de #ifdef par exemple pour revenir à 31-__builtin_clz. La plupart des ISA non x86, si elles ont un bitcan de début zéro, comptent les zéros de tête au lieu de vous donner l'index de bit. C'est pourquoi GNU C définit __builtin_clz Comme le module intégré portable. (S'il n'y a pas de prise en charge matérielle sur le système cible, le module intégré se compilera en émulation logicielle, appelant généralement une fonction d'aide libgcc .)

#include <stdint.h>

// define BSR32() and BSR64()
#if defined(_MSC_VER) || defined(__INTEL_COMPILER)
    #ifdef __INTEL_COMPILER
        typedef unsigned int bsr_idx_t;
    #else
        #include <intrin.h>   // MSVC
        typedef unsigned long bsr_idx_t;
    #endif

    static inline
    unsigned BSR32(unsigned long x){
        bsr_idx_t idx;
        _BitScanReverse(&idx, x); // ignore bool retval
        return idx;
    }
    static inline
    unsigned BSR64(uint64_t x) {
        bsr_idx_t idx;
        _BitScanReverse64(&idx, x); // ignore bool retval
        return idx;
    }
#Elif defined(__GNUC__)

  #ifdef __clang__
    static inline unsigned BSR64(uint64_t x) {
        return 63-__builtin_clzll(x);
      // gcc/ICC can't optimize this back to just BSR, but clang can and doesn't provide alternate intrinsics
    }
  #else
    #define BSR64 __builtin_ia32_bsrdi
  #endif

    #include <x86intrin.h>
    #define BSR32(x) _bit_scan_reverse(x)

#endif

bsf n'a probablement pas besoin d'autant d'aide pour les compilateurs, car le code intégré correspond au comportement de l'instruction asm consistant à renvoyer l'indice binaire du LSB, c'est-à-dire le nombre de zéros à la fin.

Un appelant de test unsigned test32(unsigned x) { return BSR32(x); } l'inline à 1 instruction sur tous les principaux compilateurs x86, sur l'explorateur du compilateur Godbolt . BSR64 s'aligne de la même manière, à une version de taille d'opérande 64 bits. Voir aussi Existe-t-il une instruction x86/x86_64 qui met à zéro tous les bits en dessous du bit le plus significatif? pour des exemples d'utilisation.

;; x64 MSVC 19.16 -O2
unsigned int test32(unsigned int) PROC                                    ; test32, COMDAT
        bsr     eax, ecx
        ret     0
unsigned int test32(unsigned int) ENDP                                    ; test32
# clang -O3 -march=haswell   is too "smart?" for its own good:
test32(unsigned int):
        lzcnt   eax, edi
        xor     eax, 31
        ret
# gcc8.2 -O3 -march=haswell
test32(unsigned int):
        bsr     eax, edi
        ret
# ICC19 -O3 -march=haswell
test32(unsigned int):
        bsr       eax, edi                                      #15.9
        ret                                                     #41.12

Le but de ceci est d'éviter le code lent de la version portable (vers non-MSVC):

#ifdef __GNUC__
unsigned badgcc(uint64_t x) {
    return 63 - __builtin_clzll(x);
}
#endif

Sans -march=haswell Nous obtenons juste BSR de clang, mais:

# gcc8.2 -O3
badgcc(unsigned long):
        bsr     rdi, rdi
        mov     eax, 63
        xor     rdi, 63
        sub     eax, edi
        ret
# ICC19.0.1 -O3
badgcc(unsigned long):
        mov       rax, -1                                       #46.17
        bsr       rdx, rdi                                      #46.17
        cmove     rdx, rax                                      #46.17
        neg       rdx                                           #46.17
        add       rdx, 63                                       #46.17
        neg       edx                                           #46.17
        add       edx, 63                                       #46.17
        mov       eax, edx                                      #46.17
        ret                                                     #46.17

C'est juste méchant. (Il est intéressant de voir que ICC fait un CMOV pour produire -1 Si l'entrée est nulle. BSR définit ZF en fonction de son entrée, contrairement à la plupart des instructions qui définissent des indicateurs en fonction du résultat. )

Avec -march=haswell (Ou autrement activer l'utilisation des instructions BMI1), ce n'est pas aussi mauvais, mais toujours pas aussi bon que juste BSR. Dépendances de sortie Modulo, que les compilateurs travaillent principalement à éviter pour lzcnt mais étrangement pas pour BSR. (Lorsque la dépendance de sortie est une dépendance true, en raison du comportement d'entrée = 0.) Pourquoi la rupture de la "dépendance de sortie" de LZCNT importe-t-elle?

1
Peter Cordes

Deux meilleures façons de le faire en C pur:

Effectuez d'abord une recherche linéaire dans le tableau d'octets/mots pour trouver le premier octet/mot différent de zéro, puis effectuez une recherche binaire non déroulée de l'octet/mot que vous trouvez.

if (b>=0x10)
  if (b>=0x40)
    if (b>=0x80) return 0;
    else return 1;
  else
    if (b>=0x20) return 2;
    else return 3;
else
  if (b>=0x4)
    if (b>=0x8) return 4;
    else return 5;
  else
    if (b>=0x2) return 6;
    else return 7;

3 (BTW c'est log2 (8)) sauts conditionnels pour obtenir la réponse. Sur les machines x86 modernes, la dernière sera optimisée en un mouvement conditionnel.

Vous pouvez également utiliser une table de recherche pour mapper l'octet à l'index du premier bit défini.

Une rubrique connexe que vous voudrez peut-être rechercher est les fonctions log2 entières. Si je me souviens bien, ffmpeg a une implémentation de Nice.

Edit: Vous pouvez réellement transformer la recherche binaire ci-dessus en une recherche binaire sans branche, mais je ne sais pas si ce serait plus efficace dans ce cas ...

1
R..

Voici un extrait de code expliquant __builtin_clz ()

////// go.c ////////
#include <stdio.h>

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)


int main()
{
  unsigned ui;

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

  return 0;
}
1
George

Um, votre balise indique 32 bits mais il semble que les valeurs que vous utilisez sont de 16 bits. Si vous vouliez dire 32 bits, je pense que la réponse pour 0x00a1 devrait être 24 et non 8.

En supposant que vous recherchez l'index de bits MSB du côté gauche et que vous savez que vous ne traiterez qu'avec des uint32_t, voici l'algorithme évident et simple:

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

int main()
{
    uint32_t test_value = 0x00a1;
    int i;

    for (i=0; i<32; ++i)
    {
        if (test_value & (0x80000000 >> i))
        {
            printf("i = %d\n", i);
            exit(0);
        }
    }

    return 0;
}
0
tikiboy

Pour Java j'utilise ceci:

static public final int msb(int n) {
    n |= n >>> 1;  
    n |= n >>> 2; 
    n |= n >>> 4; 
    n |= n >>> 8; 
    n |= n >>> 16; 
    n >>>= 1;
    n += 1; 
    return n;
}

Et:

static public final int msb_index(int n) {

    final int[] multiply_de_bruijn_bit_position = {
        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
    };
    return multiply_de_bruijn_bit_position[(msb(n) * 0x077CB531) >>> 27];
}
0
clankill3r

Voici un algorithme de force brute simple pour un tableau d'octets de taille arbitraire:

int msb( unsigned char x);  // prototype for function that returns 
                            //  most significant bit set

unsigned char* p;

for (p = arr + num_elements; p != arr;) {
    --p;
    if (*p != 0) break;
}

// p is with pointing to the last byte that has a bit set, or
//  it's pointing to the first byte in the array

if (*p) {
    return ((p - arr) * 8) + msb( *p);
}

// what do you want to return if no bits are set?
return -1;

Je vais laisser comme un exercice pour le lecteur de trouver une fonction msb() appropriée ainsi que l'optimisation pour travailler sur int ou long long Les données.

0
Michael Burr