====== 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.