std.i128 — 128-Bit-Ganzzahlen

128 Bit als Wortpaar: das volle 64×64-Produkt, Division mit Rest, Dezimalein- und -ausgabe bis 39 Stellen und (a·b)/c ohne Zwischenüberlauf.

Mathematik-Units · std.bits · std.bignum · std.sat

import std.i128;

Schicht 1, hängt an std.bits. 44 Funktionen. Alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt.


Darstellung: kein Typ, sondern ein Puffer

<WRAP alert> I128 ist kein Datentyp. Es gibt kein var x: I128. Eine 128-Bit-Zahl ist ein 16-Byte-Puffer, den der Aufrufer stellt, und alle Funktionen nehmen dessen Adresse als int64:

var a: int64 := alloc(16);
I128SetI64(a, 1000000000000000000);

Das Ergebnis einer Rechnung geht in einen weiteren Puffer: I128Mul(a, b, out). Wer out mit a gleichsetzt, überschreibt einen Operanden während der Rechnung. </WRAP>

I128Lo(p) und I128Hi(p) liefern die beiden Wörter einzeln. Diese Wörter sind Bitmuster, keine Zahlen — ein Vergleich darauf muss vorzeichenlos sein (I128LtU64), denn 2⁶⁴−1 ist als int64 gelesen negativ.


Wofür das gut ist

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

fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn ZD(t: pchar, p: int64): void {
    var b: int64 := alloc(64);
    I128ToDecimalS(p, b, 64);
    Print(t); PrintLn(b as pchar);
}

fn main(): int64 {
    var a: int64 := alloc(16);
    var b: int64 := alloc(16);
    var r: int64 := alloc(16);

    I128SetU64(a, 12345678901234567890);
    I128SetU64(b, 9876543210987654321);
    I128Mul(a, b, r);
    ZD("Produkt ueber 128 Bit : ", r);

    var roh: int64 := 12345678901234567890 * 9876543210987654321;
    ZI("dasselbe roh in int64 : ", roh);

    var okAddr: int64 := alloc(8);
    var md: int64 := I128MulDivU(1000000000000000000, 3, 7, okAddr);
    ZI("MulDivU(1e18, 3, 7)   : ", md);
    ZI("  ok                  : ", peek64(okAddr));

    I128SetI64(a, 1000000000000000000);
    I128Mul(a, a, r);
    ZD("1e18 * 1e18           : ", r);

    var q: int64 := alloc(16);
    var rem: int64 := alloc(16);
    I128SetI64(b, 7);
    I128DivRemS(r, b, q, rem);
    ZD("  geteilt durch 7     : ", q);
    ZD("  Rest                : ", rem);

    ZI("PopCount(1e36)        : ", I128PopCount(r));
    ZI("HighestSet(1e36)      : ", I128HighestSet(r));
    ZI("FitsI64(1e36)         : ", I128FitsI64(r) as int64);
    return 0;
}

Produkt ueber 128 Bit : 121932631137021795223746380111126352690
dasselbe roh in int64 : 133124662968603442
MulDivU(1e18, 3, 7)   : 428571428571428571
  ok                  : 1
1e18 * 1e18           : 1000000000000000000000000000000000000
  geteilt durch 7     : 142857142857142857142857142857142857
  Rest                : 1
PopCount(1e36)        : 44
HighestSet(1e36)      : 119
FitsI64(1e36)         : 0

Sämtliche Werte stimmen mit der Referenzrechnung überein — einschließlich des Überlaufs: 133124662968603442 ist genau das, was von dem 39-stelligen Produkt in 64 Bit übrigbleibt. Ohne Meldung, ohne Anzeichen.


''MulDivU'' — der häufigste Anwendungsfall

I128MulDivU(a, b, c, okAddr) rechnet (a · b) / c und gibt das Ergebnis als int64 zurück. Der Zwischenwert a · b läuft dabei über 128 Bit, das Endergebnis passt wieder in 64.

Genau diese Form braucht man ständig: Prozentrechnung, Umrechnungskurse, Verhältnisse, Skalierung. 1e18 · 3 / 7 ist roh nicht ausrechenbar — 1e18 · 3 passt schon nicht mehr —, über 128 Bit dagegen problemlos.

okAddr ist zu prüfen. Passt das Endergebnis nicht in int64 oder ist c null, steht dort 0 und der Rückgabewert ist bedeutungslos.


Funktionsübersicht

Gruppe Funktionen
Setzen und Lesen I128Set · I128SetU64 · I128SetI64 · I128Zero · I128Copy · I128Lo · I128Hi
Prüfen I128IsZero · I128IsNeg · I128FitsU64 · I128FitsI64
Grundrechenarten I128Add · I128Sub · I128Mul · I128MulU64 · I128Neg · I128Abs
Volles 64×64-Produkt I128MulHiU · I128MulHiS · I128MulLo · I128Mul64U · I128Mul64S · I128MulOverflowsU
Division I128DivRemU · I128DivRemS · I128DivRemU64 · I128MulDivU · I128AvgU
Bits I128Shl · I128ShrU · I128Sar · I128PopCount · I128HighestSet · I128TestBit · I128SetBit
Vergleich I128CmpU · I128CmpS · I128Equals · I128LtU64
Überträge I128AddCarry · I128SubBorrow
Text I128ToDecimalU · I128ToDecimalS · I128FromDecimal

I128ToDecimalS behandelt die Zahl als vorzeichenbehaftet, I128ToDecimalU als vorzeichenlos — bei gesetztem obersten Bit ergeben die beiden völlig verschiedene Ausgaben. Der Puffer sollte 40 Byte fassen (39 Stellen plus Vorzeichen plus Null).

I128AvgU(a, b) bildet den Mittelwert ohne Überlauf des Zwischenergebnisses — bei Binärsuchen über den vollen int64-Bereich der richtige Weg.


Warum Bibliothek und nicht Sprachtyp

 
Ein echter int128 war eine bewusste Entscheidung dagegen. Die Hardware liefert das volle 64×64-Produkt bereits in einem Registerpaar, der Codegen wirft die obere Hälfte aber weg. Ein Sprachtyp zöge Registerpaare, ABI, sämtliche Operatoren, Literale, Umwandlungen, vorkompilierte Units und Debug-Informationen nach sich — und eine 128÷128-Division gibt es in Hardware ohnehin nicht.

Die dafür nötigen Builtins gibt es seit #1552 (deckungsgleich mit #1548): mulhi(a, b) und umulhi(a, b) liefern die oberen 64 Bit des Produkts, divrem128(hi, lo, d, remAddr) teilt 128 durch 64 und legt den Rest an remAddr ab. Vorher verwarf der Codegen die obere Hälfte, weil er die Zwei-Operanden-Form imul rax, rbx benutzte.

Abgrenzung

Bereich Unit
bis 2⁶³−1 roh, mit std.sat gegen Überlauf
128 Bit std.i128
beliebig groß std.bignum
exakte Brüche std.rat
Geldbeträge std.money

Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; Produkt, Division, Rest, Popcount und der rohe Überlauf gegen eine unabhängige Referenzrechnung geprüft.