====== std.sat — Sättigungsarithmetik ======
Rechnen, das an der Bereichsgrenze **stehenbleibt** statt umzuschlagen — und geprüfte Varianten, die den Überlauf melden, bevor er passiert.
→ [[lyx_-_programmiersprache:units:mathematik|Mathematik-Units]] · [[lyx_-_programmiersprache:units:money|std.money]] · [[lyx_-_programmiersprache:units:i128|std.i128]] · [[lyx_-_programmiersprache:sprache:datentypen|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.
**''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.
----
===== 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: [[lyx_-_programmiersprache:units:i128|std.i128]] rechnet ''(a·b)/c'' über 128 Bit, ohne dass die Multiplikation überläuft.
* Geht es um **Geld**: [[lyx_-_programmiersprache:units:money|std.money]] setzt auf ''std.sat'' auf und ergänzt Rundungsarten und Aufteilen ohne Centverlust.
* Sind die Zahlen **beliebig groß**: [[lyx_-_programmiersprache:units:bignum|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.