web-dev-qa-db-fra.com

Algorithme pour générer un masque de bits

Je faisais face à ce problème unique de générer un masque de bits basé sur le paramètre d'entrée. Par exemple,

si param = 2, alors le masque sera 0x3 (11b) si param = 5, alors le masque sera 0x1F (1 1111b)

Ce que j'ai mis en œuvre en utilisant une boucle for en C, quelque chose comme

int nMask = 0;
for (int i = 0; i < param; i ++) {

    nMask |= (1 << i);
}

Je voudrais savoir s'il y a un meilleur algorithme ~~~

32
Alphaneo

Une chose à noter à propos de ces masques, c'est qu'ils sont toujours un moins qu'une puissance de deux.

L'expression 1 << n est le moyen le plus simple d'obtenir le n-ième pouvoir de deux.

Vous ne voulez pas que Zero fournisse un masque de bits de 00000001, vous voulez qu'il fournisse zéro. Donc, vous devez soustraire un.

mask = (1 << param) - 1;

Modifier:

Si vous voulez un cas spécial pour param> 32:

int sizeInBits = sizeof(mask) * BITS_PER_BYTE; // BITS_PER_BYTE = 8;
mask = (param >= sizeInBits ? -1 : (1 <<  param) - 1);

Cette méthode devrait fonctionner pour les entiers 16, 32 ou 64 bits, mais vous devrez peut-être taper explicitement le «1».

70
John Gietzen

Mise en œuvre efficace, sans branche, portable et générique (mais laide)

C:

#include <limits.h>     /* CHAR_BIT */

#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
    ((__TYPE__) (-((__ONE_COUNT__) != 0))) \
    & (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))

C++:

#include <climits>

template <typename R>
static constexpr R bitmask(unsigned int const onecount)
{
//  return (onecount != 0)
//      ? (static_cast<R>(-1) >> ((sizeof(R) * CHAR_BIT) - onecount))
//      : 0;
    return static_cast<R>(-(onecount != 0))
        & (static_cast<R>(-1) >> ((sizeof(R) * CHAR_BIT) - onecount));
}

Utilisation (production de constantes de temps de compilation)

BIT_MASK(unsigned int, 4) /* = 0x0000000f */

BIT_MASK(uint64_t, 26) /* = 0x0000000003ffffffULL */

Exemple

#include <stdio.h>

int main()
{
    unsigned int param;
    for (param = 0; param <= 32; ++param)
    {
        printf("%u => 0x%08x\n", param, BIT_MASK(unsigned int, param));
    }
    return 0;
}

Sortie

0 => 0x00000000
1 => 0x00000001
2 => 0x00000003
3 => 0x00000007
4 => 0x0000000f
5 => 0x0000001f
6 => 0x0000003f
7 => 0x0000007f
8 => 0x000000ff
9 => 0x000001ff
10 => 0x000003ff
11 => 0x000007ff
12 => 0x00000fff
13 => 0x00001fff
14 => 0x00003fff
15 => 0x00007fff
16 => 0x0000ffff
17 => 0x0001ffff
18 => 0x0003ffff
19 => 0x0007ffff
20 => 0x000fffff
21 => 0x001fffff
22 => 0x003fffff
23 => 0x007fffff
24 => 0x00ffffff
25 => 0x01ffffff
26 => 0x03ffffff
27 => 0x07ffffff
28 => 0x0fffffff
29 => 0x1fffffff
30 => 0x3fffffff
31 => 0x7fffffff
32 => 0xffffffff

Explication

Tout d’abord, comme cela a déjà été discuté dans d’autres réponses, >> est utilisé à la place de << afin d’éviter le problème lorsque le nombre de décalage est égal au nombre de bits du type de stockage de la valeur. (Merci réponse de Julien ci-dessus pour l'idée)

Pour faciliter la discussion, "instancions" la macro avec unsigned int sous la forme __TYPE__ et voyons ce qui se passe (en supposant que la version 32 bits est la même pour le moment):

((unsigned int) (-((__ONE_COUNT__) != 0))) \
& (((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))

Concentrons nous sur:

((sizeof(unsigned int) * CHAR_BIT)

premier. sizeof(unsigned int) est connu au moment de la compilation. Il est égal à 4 selon notre hypothèse. CHAR_BIT représente le nombre de bits par char, a.k.a. par octet. Il est également connu au moment de la compilation. Il est égal à 8 sur la plupart des machines sur la Terre. Comme cette expression est connue au moment de la compilation, le compilateur ferait probablement la multiplication au moment de la compilation et la traiterait comme une constante, ce qui équivaut à 32 dans ce cas.

Passons à:

((unsigned int) -1)

Il est égal à 0xFFFFFFFF. La conversion de -1 en n'importe quel type non signé produit une valeur "all-1" dans ce type. Cette partie est également une constante de temps de compilation.

Jusqu'à présent, l'expression:

(((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))

est en fait la même chose que:

0xffffffffUL >> (32 - param)

qui est la même que la réponse de Julien ci-dessus. Un problème avec sa réponse est que si param est égal à 0, produisant l'expression 0xffffffffUL >> 32, le résultat de l'expression serait 0xffffffffUL, au lieu du 0 attendu! (C’est pourquoi j’appelle mon paramètre comme __ONE_COUNT__ pour souligner son intention)

Pour résoudre ce problème, nous pourrions simplement ajouter un cas spécial pour __ONE_COUNT égal à 0 en utilisant if-else ou ?:, comme ceci:

#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
    (((__ONE_COUNT__) != 0) \
    ? (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))
    : 0)

Mais le code sans branche est plus cool, non?! Passons à la partie suivante:

((unsigned int) (-((__ONE_COUNT__) != 0)))

Commençons de l'expression la plus intérieure à la plus extrême. ((__ONE_COUNT__) != 0) produit 0 lorsque le paramètre est 0 ou 1 sinon. (-((__ONE_COUNT__) != 0)) produit 0 lorsque le paramètre est 0 ou -1 sinon. Pour ((unsigned int) (-((__ONE_COUNT__) != 0))), l'astuce de transtypage ((unsigned int) -1) est déjà expliquée ci-dessus. Avez-vous remarqué le truc maintenant? L'expression:

((__TYPE__) (-((__ONE_COUNT__) != 0)))

est égal à "tous-0" si __ONE_COUNT__ est égal à zéro, et "tous-1" sinon. Il agit comme un masque de bits pour la valeur que nous avons calculée à la première étape. Donc, si __ONE_COUNT__ est différent de zéro, le masque n'a pas d'effet et correspond à la réponse de Julien. Si __ONE_COUNT__ est 0, il masque tous les éléments de la réponse de Julien, produisant un zéro constant. Pour visualiser, regardez ceci:

__ONE_COUNT__ :                           0                Other
                                          -------------    --------------
(__ONE_COUNT__)                           0 = 0x000...0    (itself)
((__ONE_COUNT__) != 0)                    0 = 0x000...0     1 = 0x000...1
((__TYPE__) (-((__ONE_COUNT__) != 0)))    0 = 0x000...0    -1 = 0xFFF...F

Vous pouvez également utiliser un décalage à droite pour éviter le problème mentionné dans la solution (1 << param) - 1.

unsigned long const mask = 0xffffffffUL >> (32 - param);

en supposant que param <= 32, bien sûr.

8
Julien Royer

Pour les personnes intéressées, il s'agit de la variante de table de consultation abordée dans les commentaires à l'autre réponse - la différence étant que cela fonctionne correctement pour un paramètre de 32. Il est assez facile d'étendre la version à 64 bits unsigned long long, si vous en avez besoin, et La vitesse ne devrait pas être très différente (si elle est appelée dans une boucle interne étroite, la table statique restera dans au moins le cache L2, et si elle s'appelle pas appelée dans une boucle interne étroite, la différence de performances ne sera pas être important).

unsigned long mask2(unsigned param)
{
    static const unsigned long masks[] = {
        0x00000000UL, 0x00000001UL, 0x00000003UL, 0x00000007UL,
        0x0000000fUL, 0x0000001fUL, 0x0000003fUL, 0x0000007fUL,
        0x000000ffUL, 0x000001ffUL, 0x000003ffUL, 0x000007ffUL,
        0x00000fffUL, 0x00001fffUL, 0x00003fffUL, 0x00007fffUL,
        0x0000ffffUL, 0x0001ffffUL, 0x0003ffffUL, 0x0007ffffUL,
        0x000fffffUL, 0x001fffffUL, 0x003fffffUL, 0x007fffffUL,
        0x00ffffffUL, 0x01ffffffUL, 0x03ffffffUL, 0x07ffffffUL,
        0x0fffffffUL, 0x1fffffffUL, 0x3fffffffUL, 0x7fffffffUL,
        0xffffffffUL };

    if (param < (sizeof masks / sizeof masks[0]))
        return masks[param];
    else
        return 0xffffffffUL; /* Or whatever else you want to do in this error case */
}

Cela vaut la peine de préciser que si vous avez besoin de la déclaration if() (car vous craignez que quelqu'un l'appelle avec param > 32), cela ne vous rapportera rien par rapport à l'alternative de l'autre réponse:

unsigned long mask(unsigned param)
{
    if (param < 32)
        return (1UL << param) - 1;
    else
        return -1;
}

La seule différence est que cette dernière version doit avoir le cas spécial param >= 32, tandis que la première ne possède que le cas spécial param > 32.

6
caf

Que diriez-vous de cela (en Java):

int mask = -1;
mask = mask << param;
mask = ~mask;

De cette façon, vous pouvez éviter les tables de consultation et le codage en dur de la longueur d'un entier.

Explanation: Un entier signé avec une valeur de -1 est représenté sous forme binaire par un tout. Décalez à gauche le nombre de fois donné pour ajouter autant de 0 à droite. Cela se traduira par un «masque inversé». Puis annulez le résultat décalé pour créer votre masque.

Cela pourrait être réduit à:

int mask = ~(-1<<param);

Un exemple: 

int param = 5;
int mask = -1;        // 11111111 (shortened for example)
mask = mask << param; // 11100000
mask = ~mask;         // 00011111
1
broadbear

Si vous craignez un débordement dans un langage de type C avec (1 << param) - 1 (lorsque param est 32 ou 64 pour le type de taille maximale, le masque devient 0 car bitshift dépasse les limites du type), une solution à laquelle je viens de penser: 

const uint32_t mask = ( 1ul << ( maxBits - 1ul ) ) | ( ( 1ul << ( maxBits - 1ul ) ) - 1ul );

Ou un autre exemple

const uint64_t mask = ( 1ull << ( maxBits - 1ull ) ) | ( ( 1ull << ( maxBits - 1ull ) ) - 1ull );

Voici une version modélisée, gardez à l'esprit que vous devriez l'utiliser avec un type R non signé:

#include <limits.h>     /* CHAR_BIT */

// bits cannot be 0
template <typename R>
static constexpr R bitmask1( const R bits )
{
    const R one = 1;
    assert( bits >= one );
    assert( bits <= sizeof( R ) * CHAR_BIT );
    const R bitShift = one << ( bits - one );
    return bitShift | ( bitShift - one );
}

Disons que max bits est 8 avec un octet, avec la première fonction de débordement nous aurions 1 << 8 == 256, qui une fois converti en octet devient 0. Avec ma fonction, nous avons 1 << 7 == 128, qu'un octet peut contenir, devient donc 1<<7 | 1<<7 - 1

Je n'ai pas compilé la fonction, elle peut donc contenir des fautes de frappe. 


Et pour le plaisir, voici Julien Royer a étoffé:

// bits can be 0
template <typename R>
static constexpr R bitmask2( const R bits )
{
    const R zero = 0;
    const R mask = ~zero;
    const R maxBits = sizeof( R ) * CHAR_BIT;
    assert( bits <= maxBits );
    return mask >> ( maxBits - bits );
}
0
leetNightshade