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 echterint128war 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)undumulhi(a, b)liefern die oberen 64 Bit des Produkts,divrem128(hi, lo, d, remAddr)teilt 128 durch 64 und legt den Rest anremAddrab. Vorher verwarf der Codegen die obere Hälfte, weil er die Zwei-Operanden-Formimul rax, rbxbenutzte.
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.
