std.sat — Sättigungsarithmetik
Rechnen, das an der Bereichsgrenze stehenbleibt statt umzuschlagen — und geprüfte Varianten, die den Überlauf melden, bevor er passiert.
→ Mathematik-Units · std.money · std.i128 · Datentypen
import std.sat;
Schicht 0, hängt nur an std.math. 47 Funktionen. Alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt.
Das Problem
Ganzzahlarithmetik in Lyx schlägt bei Überlauf stillschweigend um, und bei einer Division kann sie das Programm beenden:
unit main;
import std.io;
import std.string;
import std.alloc;
import std.sat;
fn Z(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn main(): int64 {
var mx: int64 := SatI64Max();
Z("SatAdd(MAX, 1) = ", SatAdd(mx, 1));
Z("roh MAX + 1 = ", mx + 1);
Z("SatSub(MIN, 1) = ", SatSub(SatI64Min(), 1));
Z("SatMul(MAX, 2) = ", SatMul(mx, 2));
Z("SatDiv(MIN, -1) = ", SatDiv(SatI64Min(), -1));
Z("SatNeg(MIN) = ", SatNeg(SatI64Min()));
return 0;
}
SatAdd(MAX, 1) = 9223372036854775807
roh MAX + 1 = -9223372036854775808
SatSub(MIN, 1) = -9223372036854775808
SatMul(MAX, 2) = 9223372036854775807
SatDiv(MIN, -1) = 9223372036854775807
SatNeg(MIN) = 9223372036854775807
Die zweite Zeile ist der Kern: MAX + 1 ergibt MIN — aus der größten darstellbaren Zahl wird die kleinste, ohne jede Meldung. In einer Summe über Messwerte, einer Byte-Zählung oder einem Zeitstempel ist das ein Vorzeichenwechsel mitten im Ergebnis.
<WRAP alert>
MIN / −1 beendet das Programm. Der Betrag von MIN ist um eins größer als MAX, das Ergebnis ist also nicht darstellbar — der Prozessor löst eine Gleitkomma-Ausnahme aus. Nachgemessen:
$ ./t_d
vor der Division
Gleitkomma-Ausnahme (Speicherabzug geschrieben)
$ echo $?
136
SatDiv liefert an dieser Stelle MAX. Wer Divisor oder Dividend nicht selbst in der Hand hat, sollte nicht roh dividieren.
</WRAP>
Drei Umgangsarten
Die Unit bietet für jede Grundrechenart drei Wege. Welcher richtig ist, hängt davon ab, was ein Überlauf bedeutet:
| Weg | Form | Wann |
|---|---|---|
| Fragen | SatAddOverflows(a, b): bool | Vorab prüfen, ohne zu rechnen |
| Prüfen | SatAddChecked(a, b, out): bool | Rechnen und erfahren, ob es gutging |
| Sättigen | SatAdd(a, b): int64 | Grenze ist ein sinnvolles Ergebnis |
unit main;
import std.io;
import std.string;
import std.alloc;
import std.sat;
fn main(): int64 {
var out: int64 := alloc(8);
var ok1: bool := SatAddChecked(SatI64Max(), 1, out);
Print("AddChecked(MAX,1): ok="); Print(IntToStr(ok1 as int64));
Print(" wert="); PrintLn(IntToStr(peek64(out)));
var ok2: bool := SatAddChecked(2, 3, out);
Print("AddChecked(2,3): ok="); Print(IntToStr(ok2 as int64));
Print(" wert="); PrintLn(IntToStr(peek64(out)));
return 0;
}
AddChecked(MAX,1): ok=0 wert=0
AddChecked(2,3): ok=1 wert=5
Bei einem Überlauf bleibt out unberührt bei 0 — der Rückgabewert ist die einzige verlässliche Auskunft. Wer ihn ignoriert und out liest, bekommt keinen Fehler, sondern eine Null.
Sättigen ist nicht immer richtig. Bei einem Lautstärkeregler oder einem Farbkanal ist „an der Grenze stehenbleiben„ die gewünschte Bedeutung. Bei einer Geldsumme oder einer Zählung ist es eine stille Verfälschung — dort gehört*Checkedhin, und der Fehlerfall nach oben gemeldet.std.moneymacht genau das.
Breiten
int64 ist der einzige Ganzzahltyp, in dem gerechnet wird; schmalere Breiten entstehen durch Sättigung beim Umwandeln:
Z("SatToI8(300) = ", SatToI8(300)); // 127
Z("SatToI8(-300) = ", SatToI8(-300)); // -128
Z("SatToU8(-5) = ", SatToU8(-5)); // 0
Z("SatClamp(50, 0, 10) = ", SatClamp(50, 0, 10)); // 10
SatToI8(300) = 127
SatToI8(-300) = -128
SatToU8(-5) = 0
SatClamp(50,0,10) = 10
| Gruppe | Funktionen |
|---|---|
| Grenzen | SatI64Max/Min · SatI32Max/Min · SatI16Max/Min · SatI8Max/Min · SatU32Max · SatU16Max · SatU8Max |
| Fragen | SatAddOverflows · SatSubOverflows · SatMulOverflows · SatDivOverflows · SatNegOverflows |
| Prüfen | SatAddChecked · SatSubChecked · SatMulChecked · SatDivChecked |
| Sättigen | SatAdd · SatSub · SatMul · SatDiv · SatNeg · SatAbs |
| Umwandeln | SatToI8/I16/I32 · SatToU8/U16/U32 |
| Passt es? | SatFitsI8/I16/I32 · SatFitsU8/U16/U32 |
| Bereich | SatClamp · SatInRange |
SatAbs(MIN) liefert MAX — auch der Betrag ist an dieser einen Stelle nicht darstellbar.
Wo es weitergeht
- Reicht
int64nicht mehr, sondern das Zwischenergebnis läuft über: std.i128 rechnet(a·b)/cüber 128 Bit, ohne dass die Multiplikation überläuft. - Geht es um Geld: std.money setzt auf
std.satauf und ergänzt Rundungsarten und Aufteilen ohne Centverlust. - Sind die Zahlen beliebig groß: std.bignum.
Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; das Verhalten von MIN / −1 und MAX + 1 einzeln nachgemessen.
