std.modmath — Restklassenarithmetik
Rechnen modulo m ohne Überlauf: erweiterter Euklid, modulares Inverses, chinesischer Restsatz, Ordnung und Primitivwurzeln, Legendre- und Jacobi-Symbol, Quadratwurzeln modulo einer Primzahl.
→ Mathematik-Units · std.prime · std.bignum · std.i128
import std.modmath;
Schicht 0, hängt nur an std.alloc. 24 Funktionen, zwei Konstanten. Alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt.
Zwei Konventionen, die man zuerst kennen muss
Der Modul ist auf 2⁶² − 1 begrenzt
MOD_MAX ist 4611686018427387903. Der Grund steht in der Quelle: die Zwischenergebnisse müssen in int64 passen, und dafür ist bei 2⁶² Schluss. ModIsValidModulus(m) prüft 1 ⇐ m ⇐ MOD_MAX; jede Funktion ruft das zuerst auf.
Wer größere Moduln braucht, ist bei std.bignum richtig.
Fehler sind ''MOD_ERR'', nicht Ausnahmen
MOD_ERR ist −1. Da alle gültigen Ergebnisse einer Restklassenrechnung im Bereich 0 … m−1 liegen, ist −1 eindeutig als Fehler erkennbar — aber nur, wenn geprüft wird. Ein ungeprüftes MOD_ERR rechnet sich als −1 still weiter.
Der Rest hat immer das richtige Vorzeichen
unit main;
import std.io;
import std.string;
import std.alloc;
import std.modmath;
fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn main(): int64 {
ZI("ModNorm(-7, 5) : ", ModNorm(-7, 5));
ZI("roh -7 % 5 : ", -7 % 5);
ZI("ModPow(2, 100, 1000007): ", ModPow(2, 100, 1000007));
ZI("ModInv(3, 11) : ", ModInv(3, 11));
ZI("ModInv(4, 8) : ", ModInv(4, 8));
ZI("MOD_ERR : ", MOD_ERR);
ZI("ModGcd(48, 18) : ", ModGcd(48, 18));
return 0;
}
ModNorm(-7, 5) : 3
roh -7 % 5 : -2
ModPow(2, 100, 1000007): 698635
ModInv(3, 11) : 4
ModInv(4, 8) : -1
MOD_ERR : -1
ModGcd(48, 18) : 6
ModNorm liefert 3, der rohe Operator −2. Der %-Operator übernimmt das Vorzeichen des Dividenden; in einer Restklasse ist der Vertreter aber immer 0 … m−1. Wer roh rechnet und den Wert als Feldindex nimmt, greift daneben.
ModInv(4, 8) ist MOD_ERR — und das ist richtig: 4 und 8 sind nicht teilerfremd, ein Inverses existiert nicht. ModInv(3, 11) = 4, denn 3·4 = 12 ≡ 1 (mod 11). Ob ein Inverses überhaupt existiert, beantwortet ModIsUnit(a, m) vorab.
ModPow potenziert per Square-and-Multiply; ModMul vermeidet den Überlauf des Zwischenprodukts. Beides ist der Grund, diese Unit statt eigener %-Rechnungen zu nehmen.
Erweiterter Euklid und chinesischer Restsatz
unit main;
import std.io;
import std.string;
import std.alloc;
import std.modmath;
fn main(): int64 {
var x: int64 := alloc(8);
var y: int64 := alloc(8);
var g: int64 := ModGcdExt(48, 18, x, y);
Print("GcdExt(48,18): g="); Print(IntToStr(g));
Print(" x="); Print(IntToStr(peek64(x)));
Print(" y="); PrintLn(IntToStr(peek64(y)));
var r: int64 := alloc(8);
var m: int64 := ModCrt2(2, 3, 3, 5, r);
Print("CRT x=2(3), x=3(5): modul="); Print(IntToStr(m));
Print(" rest="); PrintLn(IntToStr(peek64(r)));
return 0;
}
GcdExt(48,18): g=6 x=-1 y=3
CRT x=2(3), x=3(5): modul=15 rest=8
ModGcdExt liefert neben dem ggT die Koeffizienten der Bézout-Identität: 48·(−1) + 18·3 = −48 + 54 = 6. Darauf beruht das modulare Inverse.
ModCrt2gibt den zusammengesetzten Modul zurück, keinen Wahrheitswert. Fürx ≡ 2 (mod 3)undx ≡ 3 (mod 5)ist die Lösungx ≡ 8 (mod 15)— der Rückgabewert15ist der neue Modul, der Rest8steht unteroutR. Im Fehlerfall — unverträgliche Kongruenzen, ungültiger Modul, oder wenn das kleinste gemeinsame VielfacheMOD_MAXüberschreitet — kommtMOD_ERR. Die Quelle prüft das kgV vor der Multiplikation, weil ein stiller Überlauf hier ein gültig aussehendes Ergebnis erzeugen würde.
ModCrtN(resids, mods, n, outR) macht dasselbe für n Kongruenzen, ModSolveLinear löst a·x ≡ b (mod m) — auch dann, wenn a nicht invertierbar ist und es mehrere Lösungen gibt.
Zahlentheorie
unit main;
import std.io;
import std.string;
import std.alloc;
import std.modmath;
fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn main(): int64 {
ZI("EulerPhi(12) : ", ModEulerPhi(12));
ZI("ModIsPrime(97) : ", ModIsPrime(97) as int64);
ZI("ModIsPrime(561) : ", ModIsPrime(561) as int64);
ZI("ModOrder(2, 7) : ", ModOrder(2, 7));
ZI("PrimitiveRoot(7) : ", ModPrimitiveRoot(7));
ZI("Legendre(2, 7) : ", ModLegendre(2, 7));
ZI("Jacobi(2, 15) : ", ModJacobi(2, 15));
ZI("ModSqrt(2, 7) : ", ModSqrt(2, 7));
return 0;
}
EulerPhi(12) : 4
ModIsPrime(97) : 1
ModIsPrime(561) : 0
ModOrder(2, 7) : 3
PrimitiveRoot(7) : 3
Legendre(2, 7) : 1
Jacobi(2, 15) : 1
ModSqrt(2, 7) : 4
ModIsPrime(561) ist 0, und das ist der interessante Fall. 561 = 3·11·17 ist eine Carmichael-Zahl — sie besteht den einfachen Fermat-Test für jede zu ihr teilerfremde Basis und sieht damit prim aus. Ein naiver Test fällt darauf herein.
| Funktion | Liefert |
|---|---|
ModEulerPhi(n) | Anzahl der zu n teilerfremden Zahlen unter n |
ModIsPrime(n) | Primzahltest bis 2⁶² |
ModOrder(a, m) | kleinstes k mit a^k ≡ 1 |
ModIsPrimitiveRoot / ModPrimitiveRoot | Erzeuger der multiplikativen Gruppe |
ModLegendre(a, p) | 1 / −1 / 0 — ist a quadratischer Rest mod Primzahl p? |
ModJacobi(a, n) | Verallgemeinerung auf zusammengesetzte n |
ModSqrt(a, p) | Quadratwurzel mod p (Tonelli-Shanks) |
ModJacobiist kein Ersatz fürModLegendre. Bei zusammengesetztemnbedeutetJacobi = 1nicht, dassaein quadratischer Rest ist — nur, dass die Anzahl der Nichtreste gerade ist. Oben istJacobi(2, 15) = 1, obwohl 2 modulo 15 kein Quadrat ist. Wer aus dem Jacobi-Symbol auf Lösbarkeit schließt, liegt falsch.
ModSqrt(2, 7) liefert 4; die zweite Wurzel ist 7 − 4 = 3. Die Funktion gibt eine der beiden zurück.
Abgrenzung
| Aufgabe | Unit |
|---|---|
| Modul bis 2⁶² − 1 | std.modmath |
| Beliebig große Zahlen | std.bignum |
| Primzahltest beliebiger Größe | std.prime |
| 128-Bit-Zwischenergebnis | std.i128 |
ModIsPrime (schnell, bis 2⁶²) und PrimeIsPrime (beliebig groß) kennen einander derzeit nicht — PrimeIsPrime delegiert für Einwort-Zahlen nicht an modmath. Wer weiß, dass die Zahl in int64 passt, nimmt ModIsPrime.
Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; sämtliche Zahlenwerte gegen eine unabhängige Referenzrechnung geprüft, die Rückgabekonventionen von ModCrt2 und MOD_ERR aus der Quelle abgelesen.
