Ich muss testen, ob die Positionen (von 0 bis 31 für eine 32-Bit-Ganzzahl) mit dem Bitwert 1 einen zusammenhängenden Bereich bilden. Zum Beispiel:
00111111000000000000000000000000 is contiguous
00111111000000000000000011000000 is not contiguous
Ich möchte, dass dieser Test, dh eine Funktion has_contiguous_one_bits(int)
, portabel ist.
Eine naheliegende Möglichkeit besteht darin, Positionen zu durchlaufen, um das erste gesetzte Bit und dann das erste nicht gesetzte Bit zu finden und nach weiteren gesetzten Bits zu suchen.
Ich frage mich, ob es einen schnelleren Weg gibt. Wenn es schnelle Methoden gibt, um die höchsten und niedrigsten gesetzten Bits zu finden (aber aus dieser Frage geht hervor, dass es keine tragbaren gibt), ist eine mögliche Implementierung möglich
bool has_contiguous_one_bits(int val)
{
auto h = highest_set_bit(val);
auto l = lowest_set_bit(val);
return val == (((1 << (h-l+1))-1)<<l);
}
Nur zum Spaß, hier sind die ersten 100 ganzen Zahlen mit zusammenhängenden Bits:
0 1 2 3 4 6 7 8 12 14 15 16 24 28 30 31 32 48 56 60 62 63 64 96 112 120 124 126 127 128 192 224 240 248 252 254 255 256 384 448 480 496 504 508 510 511 512 768 896 960 992 1008 1016 1020 1022 1023 1024 1536 1792 1920 1984 2016 2032 2040 2044 2046 2047 2048 3072 3584 3840 3968 4032 4064 4080 4088 4092 4094 4095 4096 6144 7168 7680 7936 8064 8128 8160 8176 8184 8188 8190 8191 8192 12288 14336 15360 15872 16128 16256 16320
sie sind (natürlich) von der Form (1<<m)*(1<<n-1)
mit nicht negativ m
und n
.
quelle
0x0
ist kompakt. Es ist einfacher, das Gegenteil zu definieren (nicht kompakt): Wenn zwei gesetzte Bits vorhanden sind, befindet sich mindestens ein nicht gesetztes Bit zwischen ihnen.h>=l
durch die (implizite) Funktionalität vonhighest_set_bit()
undlowest_set_bit()
Antworten:
static _Bool IsCompact(unsigned x) { return (x & x + (x & -x)) == 0; }
Kurz:
x & -x
gibt das niedrigste gesetzte Bit anx
(oder Null, wennx
Null ist).x + (x & -x)
konvertiert die niedrigste Zeichenfolge aufeinanderfolgender Einsen in eine einzelne 1 (oder umschließt sie in Null).x & x + (x & -x)
löscht diese 1 Bits.(x & x + (x & -x)) == 0
testet, ob noch 1 Bit übrig sind.Länger:
-x
gleich~x+1
, unter Verwendung des Zweierkomplements, das wir annehmen. Nachdem die Bits eingeklappt wurden~x
, werden durch Hinzufügen von 1 Übertragungen die niedrigen 1 Bits~x
und das erste 0-Bit zurückgespult, aber dann gestoppt. Somit sind die niedrigen Bits von-x
bis einschließlich seiner ersten 1 dieselben wie die niedrigen Bits vonx
, aber alle höheren Bits werden umgedreht. (Beispiel:~10011100
gibt01100011
und addiert 1 ergibt01100100
, so dass die niedrigen100
Werte gleich sind, aber die hohen10011
Werte umgedreht werden01100
.) Dann erhaltenx & -x
wir das einzige Bit, das in beiden 1 ist, nämlich das niedrigste 1-Bit (00000100
). (Wennx
Null ist,x & -x
ist Null.)Wenn Sie dies
x
hinzufügen, werden alle aufeinanderfolgenden Einsen übertragen und in Nullen geändert. Es wird eine 1 am nächsthöheren 0-Bit belassen (oder das obere Ende durchlaufen, wobei eine umschlossene Summe von Null übrig bleibt) (10100000
.)Wenn dies mit UND verknüpft ist
x
, gibt es Nullen an den Stellen, an denen die Einsen in Nullen geändert wurden (und an denen der Übertrag eine 0 in eine 1 geändert hat). Das Ergebnis ist also nicht nur dann Null, wenn es noch 1 Bit höher liegt.quelle
x & -x
in einer einzigenblsi
Anweisung, die 1 UOP bei Intel und 2 UOP bei AMD Zen ist. godbolt.org/z/5zBx-A . Aber ohne BMI1 ist die Version von @ KevinZ noch effizienter._Bool
ist ein Standardschlüsselwort gemäß C 2018 6.4.1 1.unsigned
. Wenn Sie den Test für ein signiertes Zweierkomplement durchführen möchtenint
, ist es am einfachsten, ihn einfach an die Routine in dieser Antwort zu übergeben und dasint
zu konvertierenunsigned
. Das ergibt das gewünschte Ergebnis. Das Anwenden der Operationsshow auf eineint
direkt signierte kann aufgrund von Überlauf- / Übertragsproblemen problematisch sein. (Wenn Sie die Ergänzung oder das Vorzeichen und die Größe eines Menschen testen möchten, ist dies eineint
andere Angelegenheit, die heutzutage größtenteils nur von theoretischem Interesse ist.)Es besteht eigentlich keine Notwendigkeit, irgendwelche Eigenheiten zu verwenden.
Drehen Sie zuerst alle Nullen vor der ersten 1. Testen Sie dann, ob der neue Wert eine Mersenne-Zahl ist. In diesem Algo wird Null auf true abgebildet.
bool has_compact_bits( unsigned const x ) { // fill up the low order zeroes unsigned const y = x | ( x - 1 ); // test if the 1's is one solid block return not ( y & ( y + 1 ) ); }
Wenn Sie Intrinsics verwenden möchten, finden Sie hier die Popcount-Methode:
bool has_compact_bits( unsigned const x ) { size_t const num_bits = CHAR_BIT * sizeof(unsigned); size_t const sum = __builtin_ctz(x) + __builtin_popcount(x) + __builtin_clz(z); return sum == num_bits; }
quelle
-mtbm
Exploitingblsfill
/blcfill
Anweisungen kompiliert wird . Es wäre die kürzeste bisher vorgeschlagene Version. Leider unterstützt fast kein Prozessor diese Befehlssatzerweiterung .Eigentlich müssen Sie keine führenden Nullen zählen. Wie von pmg in den Kommentaren vorgeschlagen, können Sie unter Ausnutzung der Tatsache, dass die gesuchten Zahlen die der Sequenz OEIS A023758 sind , dh Zahlen der Form 2 ^ i - 2 ^ j mit i> = j , nur nachfolgende Nullen zählen ( dh j - 1 ), schalten Sie diese Bits auf den ursprünglichen Wert um (entspricht 2 ^ j - 1 ) und prüfen Sie dann, ob dieser Wert die Form 2 ^ i - 1 hat . Mit GCC / Clang Intrinsics,
bool has_compact_bits(int val) { if (val == 0) return true; // __builtin_ctz undefined if argument is zero int j = __builtin_ctz(val) + 1; val |= (1 << j) - 1; // add 2^j - 1 val &= (val + 1); // val set to zero if of the form (2^i - 1) return val == 0; }
Diese Version ist etwas schneller als Ihre und die von KamilCuk vorgeschlagene und die von Yuri Feldman nur mit Popcount.Wenn Sie C ++ verwenden 20, können Sie eine portable Funktion erhalten durch Ersetzen
__builtin_ctz
mitstd::countr_zero
:#include <bit> bool has_compact_bits(int val) { int j = std::countr_zero(static_cast<unsigned>(val)) + 1; // ugly cast val |= (1 << j) - 1; // add 2^j - 1 val &= (val + 1); // val set to zero if of the form (2^i - 1) return val == 0; }
Die Besetzung ist hässlich, aber es warnt Sie, dass es besser ist, mit vorzeichenlosen Typen zu arbeiten, wenn Sie Bits bearbeiten. Pre-C ++ 20 Alternativen sind
boost::multiprecision::lsb
.Bearbeiten:
Der Benchmark für die durchgestrichene Verbindung wurde durch die Tatsache begrenzt, dass für die Yuri Feldman-Version keine Popcount-Anweisung ausgegeben wurde. Beim Versuch, sie auf meinem PC mit zu kompilieren
-march=westmere
, habe ich die folgende Zeit für 1 Milliarde Iterationen mit identischen Sequenzen von gemessenstd::mt19937
:__builtin_popcount
): 4.1 sZumindest in meiner Architektur scheint die schnellste die mit Popcount zu sein.
Bearbeiten 2:
Ich habe meinen Benchmark mit der neuen Version von Eric Postpischil aktualisiert. Wie in den Kommentaren angefordert, finden Sie den Code meines Tests hier . Ich habe eine No-Op-Schleife hinzugefügt, um die vom PRNG benötigte Zeit abzuschätzen. Ich habe auch die beiden Versionen von KevinZ hinzugefügt. Code wurde auf clang with
-O3 -msse4 -mbmi
to getpopcnt
undblsi
Anweisung kompiliert (danke an Peter Cordes).Ergebnisse: Zumindest in meiner Architektur ist Eric Postpischils Version genauso schnell wie die von Yuri Feldman und mindestens zweimal schneller als jede andere bisher vorgeschlagene Version.
quelle
return (x & x + (x & -x)) == 0;
.gcc -O3 -march=nehalem
(um popcnt verfügbar zu machen) oder weniger, wenn BMI1blsi
verfügbar ist fürx & -x
: godbolt.org/z/zuyj_f . Und die Anweisungen sind alle einfach, mit Ausnahme derpopcnt
Yuri-Version mit einer Latenz von 3 Zyklen. (Aber ich nehme an, Sie haben den Durchsatz auf der Bank gemessen.) Ich gehe auch davon aus, dass Sie denand val
von Yuri entfernt haben müssen, sonst wäre er langsamer.mov
und nutzt diese nicht auslea
): godbolt.org/z/5jeQLQ . Mit BMI1 ist Erics Version auf x86-64 immer noch besser, zumindest auf Intel, woblsi
es ein einzelnes UOP gibt, aber es sind 2 Uops auf AMD.Ich bin mir nicht sicher, ob ich schnell bin, kann aber einen Einzeiler erstellen, indem ich überprüfe, ob
val^(val>>1)
höchstens 2 Bits aktiviert sind.Dies funktioniert nur mit vorzeichenlosen Typen: Eine Verschiebung nach
0
oben (logische Verschiebung) ist erforderlich, keine arithmetische Rechtsverschiebung, die eine Kopie des Vorzeichenbits verschiebt.#include <bitset> bool has_compact_bits(unsigned val) { return std::bitset<8*sizeof(val)>((val ^ (val>>1))).count() <= 2; }
Zu verwerfen
0
(dh nur Eingaben zu akzeptieren, die genau 1 zusammenhängende Bitgruppe haben), logisches UND mit einemval
Wert ungleich Null. Andere Antworten auf diese Frage gelten0
als kompakt.bool has_compact_bits(unsigned val) { return std::bitset<8*sizeof(val)>((val ^ (val>>1))).count() <= 2 and val; }
C ++ macht Popcount portable über
std::bitset::count()
oder in C ++ 20 überstd::popcount
verfügbar . C verfügt immer noch nicht über eine tragbare Methode, die zuverlässig zu einem Popcnt oder einer ähnlichen Anweisung auf Zielen kompiliert werden kann, auf denen eine verfügbar ist.quelle
11011111
. Die Arithmetik wird nach rechts verschoben,11101111
und das XOR ist00110000
. Mit der logischen Rechtsverschiebung (Verschiebung in a0
oben) erhalten10110000
und erkennen Sie die mehreren Bitgruppen korrekt. Bearbeiten, um das zu beheben.__builtin_popcount()
, jeder Compiler hat heutzutage ein solches Grundelement), ist dies bei weitem der schnellste (auf einer modernen CPU). In der Tat werde ich argumentieren, dass diese Präsentation ernsthaft wichtig ist, da auf einer CPU, die POPCNT nicht als einzelne Anweisung hat, meine Implementierung dies möglicherweise übertreffen könnte. Wenn Sie diese Implementierung verwenden möchten, sollten Sie daher nur das Intrinsic verwenden.std::bitset
hat eine schreckliche Schnittstelle.CPUs haben dafür sehr schnell spezielle Anweisungen. Auf dem PC sind sie BSR / BSF (eingeführt in 80386 im Jahr 1985), auf ARM sind sie CLZ / CTZ
Verwenden Sie eins, um den Index des niedrigstwertigen gesetzten Bits zu ermitteln, und verschieben Sie die Ganzzahl um diesen Betrag nach rechts. Verwenden Sie einen anderen, um einen Index des höchstwertigen gesetzten Bits zu finden, und vergleichen Sie Ihre Ganzzahl mit (1u << (bsr + 1)) - 1.
Leider reichten 35 Jahre nicht aus, um die C ++ - Sprache an die Hardware anzupassen. Um diese Anweisungen aus C ++ zu verwenden, benötigen Sie Intrinsics, diese sind nicht portierbar und geben Ergebnisse in leicht unterschiedlichen Formaten zurück. Verwenden Sie Präprozessor
#ifdef
usw., um den Compiler zu erkennen, und verwenden Sie dann die entsprechenden Eigenschaften. In MSVC sie sind_BitScanForward
,_BitScanForward64
,_BitScanReverse
,_BitScanReverse64
. In GCC und Clang sind sie__builtin_clz
und__builtin_ctz
.quelle
std::countr_zero
undstd::countl_zero
. Wenn Sie Boost verwenden, hat es tragbare Wrapper namensboost::multiprecision::lsb
undboost::multiprecision::msb
.#include <bit>
en.cppreference.com/w/cpp/header/bit mit Bit-Scan, Popcount und Rotation hinzu. Es ist erbärmlich, dass es so lange gedauert hat, Bit-Scan portabel zu machen, aber jetzt ist es besser als nie. (Portable Popcnt war über verfügbarstd::bitset::count()
.) In C ++ 20 fehlen noch einige Dinge, die Rust bereitstellt ( doc.rust-lang.org/std/primitive.i32.html ), z. B. Bit-Reverse und Endian, die einige CPUs effizient bereitstellen aber nicht alles. Eine tragbare Funktion für einen Vorgang, über den alle CPUs verfügen, ist sinnvoll, obwohl Benutzer wissen müssen, was schnell ist.Der Vergleich mit Nullen anstelle von Einsen spart einige Operationen:
bool has_compact_bits2(int val) { if (val == 0) return true; int h = __builtin_clz(val); // Clear bits to the left val = (unsigned)val << h; int l = __builtin_ctz(val); // Invert // >>l - Clear bits to the right return (~(unsigned)val)>>l == 0; }
Das Folgende führt zu einer Anweisung weniger als die oben genannten
gcc10 -O3
auf x86_64 und verwendet die Zeichenerweiterung:bool has_compact_bits3(int val) { if (val == 0) return true; int h = __builtin_clz(val); val <<= h; int l = __builtin_ctz(val); return ~(val>>l) == 0; }
Auf Godbolt getestet .
quelle
~val<<h>>h>>l == 0
, dass Sie das tun, was Sie denken?there exists a faster way?
und angenommen habe, dass alles geht.Sie können die Anforderung umformulieren:
Das Durchgehen aller Teile könnte folgendermaßen aussehen:
unsigned int count_bit_changes (uint32_t value) { unsigned int bit; unsigned int changes = 0; uint32_t last_bit = value & 1; for (bit = 1; bit < 32; bit++) { value = value >> 1; if (value & 1 != last_bit { changes++; last_bit = value & 1; } } return changes; }
Dies kann aber sicherlich optimiert werden (z. B. durch Abbrechen der
for
Schleife beivalue
Erreichen,0
was bedeutet, dass keine signifikanten Bits mit dem Wert 1 mehr vorhanden sind).quelle
Sie können diese Abfolge von Berechnungen durchführen (vorausgesetzt,
val
als Eingabe):uint32_t x = val; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16;
um eine Zahl mit allen Nullen unter der höchstwertigen
1
mit Einsen gefüllten zu erhalten.Sie können auch berechnen
y = val & -val
, dass alle außer dem niedrigstwertigen 1-Bit inval
(z. B.7 & -7 == 1
und12 & -12 == 4
) entfernt werden sollen.Warnung: Dies schlägt fehl
val == INT_MIN
, daher müssen Sie diesen Fall separat behandeln, dies erfolgt jedoch sofort.Verschieben Sie dann
y
um eine Position nach rechts , um etwas unter das tatsächliche LSB von zu gelangenval
, und führen Sie die gleiche Routine aus wie fürx
:uint32_t y = (val & -val) >> 1; y |= y >> 1; y |= y >> 2; y |= y >> 4; y |= y >> 8; y |= y >> 16;
Dann
x - y
oderx & ~y
oderx ^ y
erzeugt die "kompakte" Bitmaske über die gesamte Länge vonval
. Vergleichen Sie es einfach, umval
zu sehen, obval
es "kompakt" ist.quelle
Wir können die in gcc integrierten Anweisungen verwenden , um zu überprüfen, ob:
Die Anzahl der gesetzten Bits
ist gleich (a - b):
a : Index des höchsten gesetzten Bits (32 - CTZ) (32, weil 32 Bits in einer vorzeichenlosen Ganzzahl).
b : Index des niedrigsten gesetzten Bits (CLZ):
Zum Beispiel, wenn n = 0b0001100110; Wir erhalten 4 mit Popcount, aber die Indexdifferenz (a - b) gibt 6 zurück.
bool has_contiguous_one_bits(unsigned n) { return (32 - __builtin_clz(n) - __builtin_ctz(n)) == __builtin_popcount(n); }
was auch geschrieben werden kann als:
bool has_contiguous_one_bits(unsigned n) { return (__builtin_popcount(n) + __builtin_clz(n) + __builtin_ctz(n)) == 32; }
Ich denke nicht, dass es eleganter oder effizienter ist als die derzeit am besten bewertete Antwort:
return (x & x + (x & -x)) == 0;
mit folgender Montage:
mov eax, edi neg eax and eax, edi add eax, edi test eax, edi sete al
aber es ist wahrscheinlich leichter zu verstehen.
quelle
Okay, hier ist eine Version, die Bits durchläuft
template<typename Integer> inline constexpr bool has_compact_bits(Integer val) noexcept { Integer test = 1; while(!(test & val) && test) test<<=1; // skip unset bits to find first set bit while( (test & val) && test) test<<=1; // skip set bits to find next unset bit while(!(test & val) && test) test<<=1; // skip unset bits to find an offending set bit return !test; }
Die ersten beiden Schleifen fanden den ersten kompakten Bereich. Die letzte Schleife prüft, ob sich außerhalb dieses Bereichs ein anderes gesetztes Bit befindet.
quelle