std.bits — Bitoperationen

Schieben und Rotieren mit vorhersagbarem Verhalten, Zählfunktionen über alle 64 Bit und Bitmengen beliebiger Größe mit Rank und Select.

Mathematik-Units · std.i128 · std.math · Rohspeicher

import std.bits;

Keine Abhängigkeiten — std.bits ist Schicht 0 und kommt ohne jede andere Unit aus. 65 Funktionen, eine Konstante (BITS_WORD = 64). Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt.


Warum nicht einfach ''%%<<%%'' und ''>>''?

Weil die Prozessorbefehle die Schiebeweite modulo 64 nehmen. Das ist kein Fehler, sondern Hardware — aber es überrascht, sobald die Weite aus einer Rechnung stammt:

unit main;
import std.io;
import std.string;
import std.bits;

fn main(): int64 {
    Print("roh   1 << 64      = "); PrintLn(IntToStr(1 << 64));
    Print("BitsShl(1, 64)     = "); PrintLn(IntToStr(BitsShl(1, 64)));
    Print("roh   1 << 65      = "); PrintLn(IntToStr(1 << 65));
    Print("BitsShl(1, 65)     = "); PrintLn(IntToStr(BitsShl(1, 65)));
    return 0;
}

roh   1 << 64      = 1
BitsShl(1, 64)     = 0
roh   1 << 65      = 2
BitsShl(1, 65)     = 0

1 << 64 ergibt 1, weil 64 mod 64 = 0 ist. BitsShl sättigt stattdessen: alles ab Breite 64 ergibt 0, so wie es mathematisch sein muss. Wer die Schiebeweite nicht selbst in der Hand hat — aus einer Feldbreite, einer Konfiguration, einer Benutzereingabe —, nimmt die Funktionen.

Roh std.bits Unterschied
a << n BitsShl(a, n) sättigt statt modulo 64
a » n BitsShr(a, n) logisch (füllt Nullen), sättigt
BitsSar(a, n) arithmetisch (füllt das Vorzeichenbit)
BitsShlChecked(a, n, ok) meldet über ok, ob Bits verlorengingen
BitsShlOverflows(a, n) fragt dasselbe, ohne zu rechnen

BitsShr und BitsSar unterscheiden sich nur bei negativen Zahlen: BitsShr(−1, 1) schiebt eine Null nach und ergibt eine große positive Zahl, BitsSar(−1, 1) bleibt bei −1.


Zählen und Suchen

unit main;
import std.io;
import std.string;
import std.bits;

fn Z(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }

fn main(): int64 {
    Z("PopCount(-1)        = ", BitsPopCount(-1));
    Z("PopCount(255)       = ", BitsPopCount(255));
    Z("Clz(1)              = ", BitsClz(1));
    Z("Ctz(8)              = ", BitsCtz(8));
    Z("Extract(0xFF, 4, 4) = ", BitsExtract(255, 4, 4));
    Z("Reverse(1)          = ", BitsReverse(1));
    Z("SwapBytes(1)        = ", BitsSwapBytes(1));
    Z("CeilPow2(100)       = ", BitsCeilPow2(100));
    return 0;
}

PopCount(-1)        = 64
PopCount(255)       = 8
Clz(1)              = 63
Ctz(8)              = 3
Extract(0xFF, 4, 4) = 15
Reverse(1)          = -9223372036854775808
SwapBytes(1)        = 72057594037927936
CeilPow2(100)       = 128

Die beiden auffälligen Zeilen sind richtig: BitsReverse(1) schiebt Bit 0 auf Bit 63 — das ist als int64 gelesen −2⁶³. BitsSwapBytes(1) macht aus dem niedrigsten Byte das höchste, also 2⁵⁶.

Funktion Liefert
BitsPopCount(x) Anzahl gesetzter Bits (SWAR über alle 64 Bit)
BitsParity(x) 1 bei ungerader Anzahl, sonst 0
BitsClz(x) / BitsCtz(x) führende / nachlaufende Nullen
BitsHighestSet(x) / BitsLowestSet(x) Index des höchsten / niedrigsten gesetzten Bits
BitsHamming(a, b) Anzahl unterschiedlicher Bits
BitsTest/Set/Clear/Toggle(x, i) einzelnes Bit lesen und setzen
BitsExtract(x, pos, len) Bitfeld herausziehen
BitsDeposit(x, pos, len, v) Bitfeld einsetzen
BitsIsPow2 · BitsFloorPow2 · BitsCeilPow2 Zweierpotenzen
BitsGrayEncode / BitsGrayDecode Gray-Code hin und zurück

Rotation in beliebiger Breite

Das ist der Teil, den der Bestand nicht hat. BitsRotlN(x, n, width) rotiert innerhalb einer frei gewählten Breite; die gängigen Breiten gibt es als fertige Namen:

Funktion Breite
BitsRotl / BitsRotr 64
BitsRotl32 / BitsRotr32 32
BitsRotl16 / BitsRotr16 16
BitsRotl8 / BitsRotr8 8
BitsRotlN / BitsRotrN beliebig
BitsRotlPair 128 Bit über ein Wortpaar
 
RotateLeft32 aus std.conv war fehlerhaft (#1546) und ist es nicht mehr. Gegen 1.0.21A geprüft: RotateLeft32 und BitsRotl32 liefern für 0x12345678 bei allen Weiten dieselben Werte, beide stimmen mit der Referenzrechnung überein. Dasselbe gilt für PopCount (#1545). Der Grund, hierher zu wechseln, ist also nicht mehr ein Defekt, sondern der größere Umfang — BitsRotlN und die Bitmengen unten.

Bitmengen

Eine Bitmenge ist ein Speicherblock, den der Aufrufer stellt. BitsetWords(n) sagt, wie viele 64-Bit-Wörter für n Bits nötig sind:

unit main;
import std.io;
import std.string;
import std.alloc;
import std.bits;

fn Z(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }

fn main(): int64 {
    var n: int64 := 200;
    var bs: int64 := alloc(BitsetWords(n) * 8);
    BitsetClearAll(bs, n);

    BitsetSet(bs, 5);
    BitsetSet(bs, 64);
    BitsetSet(bs, 199);

    Z("Count      = ", BitsetCount(bs, n));
    Z("Rank(100)  = ", BitsetRank(bs, n, 100));
    Z("Select(1)  = ", BitsetSelect(bs, n, 1));
    Z("Next(6)    = ", BitsetNext(bs, n, 6));
    return 0;
}

Count      = 3
Rank(100)  = 2
Select(1)  = 64
Next(6)    = 64

Rank und Select sind das Paar, auf das es ankommt. BitsetRank(bs, n, i) zählt die gesetzten Bits vor Position i — bei 100 sind das die Bits 5 und 64, also 2. BitsetSelect(bs, n, k) geht den umgekehrten Weg und liefert die Position des k-ten gesetzten Bits, nullbasiert: Select(1) ist das zweite, also 64. BitsetNext(bs, n, from) findet das nächste gesetzte Bit ab einer Position — damit lässt sich eine dünn besetzte Menge durchlaufen, ohne alle 200 Positionen anzufassen.

Gruppe Funktionen
Anlegen BitsetWords · BitsetClearAll · BitsetSetAll
Einzelbit BitsetTest · BitsetSet · BitsetClear · BitsetToggle
Abfragen BitsetCount · BitsetIsEmpty · BitsetRank · BitsetSelect · BitsetNext
Mengenverknüpfung BitsetAnd · BitsetOr · BitsetXor · BitsetAndNot · BitsetNot
Vergleich BitsetEquals · BitsetSubset
Zählen ohne Zwischenergebnis BitsetCountAnd · BitsetCountOr · BitsetHamming
Verschieben BitsetShl · BitsetShr · BitsetRotl · BitsetRotr

BitsetCountAnd(a, b, n) zählt die Bits im Schnitt, ohne den Schnitt zu bilden — für Ähnlichkeitsmaße über große Mengen der übliche Weg, weil kein Zielpuffer gebraucht wird.

 
Die Größe n wird nicht mitgeführt. Jede Bitset*-Funktion bekommt sie als Argument, und keine prüft sie gegen den tatsächlich reservierten Speicher. Wer alloc(BitsetWords(n) * 8) rechnet und später mit einem größeren n zugreift, schreibt in fremden Speicher. Die Größe gehört neben die Menge — als Konstante oder als Feld in der eigenen Struktur.

Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; Schiebe-, Zähl- und Rotationswerte gegen eine unabhängige Referenzrechnung geprüft.