std.rat — exakte Brüche

Rationale Zahlen auf zwei int64. Jeder Überlauf wird gemeldet statt abgeschnitten; beste Näherung mit Semikonvergenten.

Mathematik-Units · std.i128 · std.money · std.feq

import std.rat;

Schicht 2, hängt an std.i128 und std.math. 42 Funktionen. Alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt.


 
Die Struct-Rückgabe legt keinen Speicher mehr an (#1580 behoben). Vorher kostete jeder Aufruf zwei mmap ohne munmap: die Kopie bei der Rückgabe und, größer noch, die Deklaration var r: Rat; mit 4096 Byte. Nachgemessen mit 1.1.2E über 200 000 Rückgaben:

^ ^ vorher (1.0.21A) ^ jetzt (1.1.2E) ^
| mmap-Aufrufe | 200 000 | 9 (nur Programmstart) |
| Speicher | 800 MB | 6,3 MB |
| Laufzeit | 3,53 s | 0,05 s |

Die Unit ist damit auch in Schleifen brauchbar; das gilt ebenso für std.complex und std.quat.

Exakt heißt exakt

unit main;
import std.io;
import std.string;
import std.alloc;
import std.rat;

fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn ZR(t: pchar, a: Rat): void {
    Print(t); Print(IntToStr(RatNum(a))); Print("/"); PrintLn(IntToStr(RatDen(a)));
}

fn main(): int64 {
    var a: Rat := RatNew(1, 3);
    var b: Rat := RatNew(1, 6);
    ZR("1/3 + 1/6      = ", RatAdd(a, b));
    ZR("1/3 * 1/6      = ", RatMul(a, b));
    ZR("1/3 / 1/6      = ", RatDiv(a, b));
    ZR("6/8 gekuerzt   = ", RatNew(6, 8));
    ZR("(1/3)^3        = ", RatPow(a, 3));

    var big: Rat := RatNew(9223372036854775807, 2);
    var sum: Rat := RatAdd(big, big);
    ZI("Ueberlauf -> IsValid: ", RatIsValid(sum) as int64);

    var pi: Rat := RatFromF64(3.14159265358979, 1000000);
    ZR("pi (maxDen 1e6)  = ", pi);
    ZR("  auf maxDen 100 = ", RatApprox(pi, 100));
    ZR("  auf maxDen 113 = ", RatApprox(pi, 113));

    var buf: int64 := alloc(64);
    RatFormatDecimal(RatNew(1, 3), 10, buf, 64);
    Print("1/3 dezimal    = "); PrintLn(buf as pchar);
    return 0;
}

1/3 + 1/6      = 1/2
1/3 * 1/6      = 1/18
1/3 / 1/6      = 2/1
6/8 gekuerzt   = 3/4
(1/3)^3        = 1/27
Ueberlauf -> IsValid: 0
pi (maxDen 1e6)  = 3126535/995207
  auf maxDen 100 = 311/99
  auf maxDen 113 = 355/113
1/3 dezimal    = 0.3333333333

1/3 + 1/6 ist genau 1/2 — kein Rundungsfehler, keine Toleranz. Jedes Ergebnis wird gekürzt: RatNew(6, 8) ist sofort 3/4.


Überlauf wird gemeldet

<WRAP alert> Zähler und Nenner sind je ein int64. Bei MAX/2 + MAX/2 passt das Ergebnis nicht mehr — die Unit liefert dann einen ungültigen Bruch statt eines falschen.

RatIsValid ist zu prüfen. Ein ungültiger Bruch hat Nenner 0 und rechnet sich stillschweigend weiter, wenn niemand hinsieht. Das ist der bewusste Entwurf: std.rat baut auf zwei int64 statt auf std.bignum, ist dafür schnell — und meldet die Grenze, statt sie zu überschreiten. </WRAP>

Wer beliebig große Zähler braucht, rechnet mit std.bignum und bildet den Bruch selbst.


Beste Näherung — mit Semikonvergenten

RatApprox(a, maxDen) sucht den Bruch mit dem größten Nenner unterhalb der Schranke, der a am nächsten kommt.

Höchstnenner Ergebnis
113 355/113 — die berühmte Näherung, 7 gültige Stellen
100 311/99
 
311/99 ist die interessante Zeile. Die naheliegende Antwort für „Nenner höchstens 100„ wäre 22/7 — der bekannteste Kettenbruch-Näherungswert. 311/99 ist aber näher an π, und ein Verfahren, das nur die Konvergenten des Kettenbruchs betrachtet, findet ihn nicht. std.rat berücksichtigt auch die Semikonvergenten und liefert deshalb tatsächlich die beste Näherung, nicht bloß eine gute.
Funktion Zweck
RatFromF64(x, maxDen) Fließkommazahl annähern
RatFromF64Exact(x) die f64-Zahl exakt als Bruch (Nenner ist eine Zweierpotenz)
RatApprox(a, maxDen) vorhandenen Bruch vereinfachen
RatContinuedFrac / RatFromContinuedFrac Kettenbruchdarstellung
RatMediant(a, b) Mediant zweier Brüche
RatSimplest(lo, hi) einfachster Bruch im Intervall

RatSimplest beantwortet die Frage „welcher Bruch mit kleinstem Nenner liegt zwischen diesen beiden Messwerten“ — die übliche Aufgabe beim Rekonstruieren eines Verhältnisses aus gerundeten Angaben.


Funktionsübersicht

Gruppe Funktionen
Erzeugen RatNew · RatFromInt · RatZero · RatOne · RatCopy · RatParse
Zerlegen RatNum · RatDen · RatSign
Prüfen RatIsValid · RatIsZero · RatIsInt
Rechnen RatAdd · RatSub · RatMul · RatDiv · RatPow · RatNeg · RatAbs · RatInv
Vergleichen RatCmp · RatEquals · RatLt · RatLe · RatMin · RatMax
Runden RatFloor · RatCeil · RatTrunc · RatRound · RatRoundHalfEven · RatFrac
Umwandeln RatToF64 · RatFromF64 · RatFromF64Exact
Text RatFormat · RatFormatDecimal
 
Der Vergleich läuft nicht über Kreuzmultiplikation in int64. a.num · b.den gegen b.num · a.den würde bei großen Zählern überlaufen und dann das falsche Vorzeichen liefern — bei zwei bestimmten Brüchen ist genau das in der Testbatterie als Negativkontrolle festgehalten. RatCmp rechnet über std.i128 und ist deshalb im ganzen Wertebereich verlässlich.

Abgrenzung

Aufgabe Unit
Exakte Brüche, moderate Größe std.rat
Geldbeträge mit fester Skala std.money
Beliebig große Ganzzahlen std.bignum
Fließkomma mit Toleranz std.feq

Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; die Näherungen gegen die bekannten Kettenbruchwerte geprüft, #1580 mit strace und Speichermessung selbst nachgemessen.