Effiziente, verzweigungsfreie, portable und generische (aber hässliche) Implementierung
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));
}
Verwendung (Erzeugen von Kompilierzeitkonstanten)
BIT_MASK(unsigned int, 4) /* = 0x0000000f */
BIT_MASK(uint64_t, 26) /* = 0x0000000003ffffffULL */
Beispiel
#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;
}
Ausgabe
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
Erläuterung
Zunächst einmal, wie bereits in anderen Antworten erörtert, >>
verwendet wird, statt <<
um das Problem zu vermeiden, dass die Anzahl der Verschiebungen gleich der Anzahl der Bits des Speichertyps des Wertes ist. (Dank Antwort von Julien oben für die Idee)
Zur Vereinfachung der Diskussion "instanziieren" wir das Makro mit unsigned int
comme __TYPE__
und sehen Sie, was passiert (wobei wir im Moment von 32-Bit ausgehen):
((unsigned int) (-((__ONE_COUNT__) != 0))) \
& (((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))
Konzentrieren wir uns darauf:
((sizeof(unsigned int) * CHAR_BIT)
Erstens. sizeof(unsigned int)
zur Kompilierzeit bekannt ist. Sie ist gleich 4
nach unseren Annahmen. CHAR_BIT
steht für die Anzahl der Bits pro char
auch bekannt als "pro Byte". Sie ist auch zur Kompilierzeit bekannt. Sie ist gleich 8
auf den meisten Maschinen der Erde. Da dieser Ausdruck zur Kompilierzeit bekannt ist, würde der Compiler wahrscheinlich die Multiplikation zur Kompilierzeit durchführen und sie als Konstante behandeln, was gleichbedeutend ist mit 32
in diesem Fall.
Gehen wir weiter zu:
((unsigned int) -1)
Sie ist gleich 0xFFFFFFFF
. Gießen -1
zu einem beliebigen vorzeichenlosen Typ erzeugt einen Wert von "all-1s" in diesem Typ. Dieser Teil ist auch eine Kompilierzeitkonstante.
Bis jetzt war der Ausdruck:
(((unsigned int) -1) >> ((sizeof(unsigned int) * CHAR_BIT) - (__ONE_COUNT__)))
ist in der Tat dasselbe wie:
0xffffffffUL >> (32 - param)
was mit der Antwort von Julien übereinstimmt. Ein Problem mit seiner Antwort ist, dass wenn param
ist gleich 0
und ergibt den Ausdruck 0xffffffffUL >> 32
wäre das Ergebnis des Ausdrucks 0xffffffffUL
anstelle des erwarteten 0
! (Deshalb nenne ich meinen Parameter als __ONE_COUNT__
um seine Absicht zu unterstreichen)
Um dieses Problem zu lösen, könnten wir einfach einen Sonderfall für __ONE_COUNT
ist gleich 0
mit if-else
o ?:
etwa so:
#define BIT_MASK(__TYPE__, __ONE_COUNT__) \
(((__ONE_COUNT__) != 0) \
? (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))
: 0)
Aber verzweigungsfreier Code ist doch viel cooler, oder?! Lassen Sie uns zum nächsten Teil übergehen:
((unsigned int) (-((__ONE_COUNT__) != 0)))
Beginnen wir mit dem innersten Ausdruck und dem äußersten. ((__ONE_COUNT__) != 0)
produziert 0
wenn der Parameter 0
, oder 1
sonst. (-((__ONE_COUNT__) != 0))
produziert 0
wenn der Parameter 0
, oder -1
anders. Für ((unsigned int) (-((__ONE_COUNT__) != 0)))
der Trick mit dem Typ-Cast ((unsigned int) -1)
wurde bereits oben erläutert. Merken Sie jetzt den Trick? Der Ausdruck:
((__TYPE__) (-((__ONE_COUNT__) != 0)))
ist gleich "all-0s", wenn __ONE_COUNT__
Null ist und ansonsten "alle-1s". Er dient als Bitmaske für den im ersten Schritt berechneten Wert. Wenn also __ONE_COUNT__
ungleich Null ist, hat die Maske keine Wirkung und entspricht der Antwort von Julien. Wenn __ONE_COUNT__
es 0
werden alle Teile von Juliens Antwort maskiert, so dass eine konstante Null entsteht. Zur Veranschaulichung sehen Sie sich das an:
__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
0 Stimmen
Ihrer Beschreibung nach wäre dies wahrscheinlich das Einfachste, was Sie tun könnten bis Sie etwas eingebaut haben :p