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 *Checked hin, und der Fehlerfall nach oben gemeldet. std.money macht 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 int64 nicht 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.sat auf 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.