====== std.numeric — numerisches Grundwerkzeug ====== Nullstellen, Integration, Interpolation und Splines, gewöhnliche Differentialgleichungen samt steifer Löser und Randwertprobleme. → [[lyx_-_programmiersprache:units:mathematik|Mathematik-Units]] · [[lyx_-_programmiersprache:units:sum|std.sum]] · [[lyx_-_programmiersprache:units:autodiff|std.autodiff]] · [[lyx_-_programmiersprache:sprache:funktionen|Funktionen]] import std.numeric; Schicht 1, hängt an [[lyx_-_programmiersprache:units:sum|std.sum]], ''std.math'' und ''std.alloc''. 34 Funktionen. Alle Beispiele mit ''lyxc 1.0.21A'' übersetzt und ausgeführt. ---- ===== Die Funktion wird übergeben ===== ''std.numeric'' arbeitet mit **Funktionszeigern**. Eine zu untersuchende Funktion wird als gewöhnliche ''fn'' geschrieben und beim Aufruf über ihren Namen weitergereicht: fn f(x: f64): f64 { return x * x - 2.0; } var wurzel: f64 := NumBrent(f, 1.0, 2.0, 0.000000001, 200); ^ Signatur ^ Wofür ^ | ''fn(f64): f64'' | Nullstellen, Integration, Ableitung | | ''fn(f64, f64): f64'' | ODE — ''f(t, y)'' | | ''fn(f64, f64, f64): f64'' | Randwertproblem — ''f(t, y, y')'' | | ''fn(f64, int64, int64): void'' | ODE-System, Zustand über Adressen | ---- ===== Nullstellen ===== unit main; import std.io; import std.string; import std.alloc; import std.math; import std.numeric; fn ZF(t: pchar, v: f64): void { Print(t); PrintF64(v); } fn f(x: f64): f64 { return x * x - 2.0; } fn df(x: f64): f64 { return 2.0 * x; } fn main(): int64 { ZF("Bisect : ", NumBisect(f, 1.0, 2.0, 0.000000001, 200)); ZF("Brent : ", NumBrent(f, 1.0, 2.0, 0.000000001, 200)); ZF("Newton : ", NumNewton(f, df, 1.0, 0.000000001, 100)); ZF("NewtonNum : ", NumNewtonNum(f, 1.0, 0.000000001, 100)); ZF("Secant : ", NumSecant(f, 1.0, 2.0, 0.000000001, 100)); ZF("sqrt(2) : ", SqrtF64(2.0)); return 0; } Bisect : 1.414213 Brent : 1.414213 Newton : 1.414213 NewtonNum : 1.414213 Secant : 1.414213 sqrt(2) : 1.414213 Alle fünf finden dieselbe Nullstelle — sie unterscheiden sich in Voraussetzungen und Geschwindigkeit: ^ Verfahren ^ Braucht ^ Verhalten ^ | ''NumBisect'' | Intervall mit Vorzeichenwechsel | **findet immer**, aber langsam | | ''NumBrent'' | dasselbe | schnell **und** garantiert — die Vorgabe | | ''NumNewton'' | Startwert **und Ableitung** | am schnellsten, kann davonlaufen | | ''NumNewtonNum'' | nur Startwert | Ableitung numerisch, entsprechend ungenauer | | ''NumSecant'' | zwei Startwerte | ohne Ableitung, meist schneller als Bisektion | > **''NumBrent'' ist fast immer die richtige Wahl.** Es verbindet die Sicherheit der Bisektion mit der Geschwindigkeit der inversen quadratischen Interpolation: es kann nicht aus dem Intervall laufen, braucht aber typisch ein Zehntel der Schritte. Newton ist nur dann besser, wenn die Ableitung **exakt** vorliegt — und dann führt der Weg über [[lyx_-_programmiersprache:units:autodiff|std.autodiff]], das sie ohne Handrechnung liefert. > > Newton **ohne** Vorzeichenwechsel-Intervall kann bei einem schlechten Startwert beliebig weit weglaufen oder zwischen zwei Punkten pendeln. Die ''maxIter''-Grenze ist deshalb kein Formalismus. ''NumDeriv(f, x)'' und ''NumDeriv2(f, x)'' liefern erste und zweite Ableitung numerisch; ''NumDerivStep(x)'' gibt die dazu passende Schrittweite. ---- ===== Integration ===== fn g(x: f64): f64 { return x * x; } // Integral 0..1 = 1/3 Trapez x^2 0..1 : 0.333333 Simpson x^2 0..1 : 0.333333 Romberg x^2 0..1 : 0.333333 Gauss5 x^2 0..1 : 0.333333 ^ Verfahren ^ Eigenschaft ^ | ''NumTrapezoid(f, a, b, n)'' | einfachste Form, fester Raster | | ''NumSimpson(f, a, b, n)'' | deutlich genauer bei gleichem Aufwand | | ''NumAdaptiveSimpson(f, a, b, tol, …)'' | verfeinert nur dort, wo es nötig ist | | ''NumRomberg(f, a, b, maxLevel, tol)'' | Extrapolation, sehr genau bei glatten Funktionen | | ''NumGauss5(f, a, b)'' | fünf Stützstellen, exakt bis Grad 9 | | ''NumGaussComposite(f, a, b, n)'' | dasselbe stückweise | **''NumGauss5'' rechnet ''x²'' mit fünf Auswertungen exakt**, während Trapez dafür tausend braucht. Der Preis: Gauß setzt eine glatte Funktion voraus. Bei einem Knick oder einer Polstelle ist ''NumAdaptiveSimpson'' die verlässlichere Wahl, weil es die Verfeinerung dorthin lenkt, wo sich die Funktion tatsächlich ändert. ---- ===== Interpolation ===== ^ Funktion ^ Zweck ^ | ''NumLerp(x0, y0, x1, y1, x)'' | zwischen zwei Punkten | | ''NumInterpLinear(xs, ys, n, x)'' | Streckenzug durch ''n'' Punkte | | ''NumLagrange(xs, ys, n, x)'' | Polynom durch alle Punkte | | ''NumSplineBuild'' + ''NumSplineEval'' | kubischer Spline | > **Lagrange ist bei vielen Punkten die falsche Wahl.** Ein Polynom durch 20 Stützstellen schwingt zwischen ihnen stark aus — das Runge-Phänomen. Für glatte Kurven durch viele Punkte ist der **Spline** richtig: ''NumSplineBuild'' rechnet einmal die zweiten Ableitungen aus (Feld ''y2''), danach ist jedes ''NumSplineEval'' billig. ---- ===== Differentialgleichungen ===== fn ode(t: f64, y: f64): f64 { return y; } // y' = y, y(0)=1 -> y(1) = e Rk4 y'=y, y(1) : 2.718281 Euler y'=y, y(1) : 2.704813 e zum Vergleich : 2.718281 **Bei gleicher Schrittzahl (100) trifft Runge-Kutta alle gezeigten Stellen, Euler weicht schon in der dritten ab.** Das ist der Unterschied zwischen einem Verfahren erster und vierter Ordnung: halbiert man die Schrittweite, wird der Fehler bei Euler halbiert, bei RK4 auf ein Sechzehntel gedrückt. ^ Gruppe ^ Funktionen ^ | Einzelschritt | ''NumEulerStep'' · ''NumMidpointStep'' · ''NumRk4Step'' | | Über ein Intervall | ''NumEulerSolve'' · ''NumRk4Solve'' · ''NumRk4Adaptive'' | | Systeme | ''NumRk4SolveSys'' | | Mehrschritt | ''NumAdamsBashforth4'' · ''NumAdamsMoulton4'' | | **Steif** | ''NumBdf1'' · ''NumBdf2'' · ''NumTrapezoidal'' | | Randwertprobleme | ''NumShoot'' · ''NumShootTrace'' · ''NumBvpLinear'' | > **Steife Gleichungen erkennt man daran, dass RK4 winzige Schritte braucht.** Wenn eine Lösung schnell abklingende und langsam veränderliche Anteile mischt, erzwingt der schnelle Anteil die Schrittweite — auch lange nachdem er praktisch verschwunden ist. Explizite Verfahren werden dann unbrauchbar langsam oder instabil. > > ''NumBdf1'', ''NumBdf2'' und ''NumTrapezoidal'' sind **implizit** und haben das Problem nicht. Sie kosten je Schritt mehr, brauchen aber Größenordnungen weniger Schritte. Typische Fälle: chemische Reaktionskinetik, elektrische Netzwerke mit sehr verschiedenen Zeitkonstanten, Regelkreise. ''NumShoot'' löst ein Randwertproblem, indem es die Anfangssteigung so lange anpasst, bis der zweite Rand getroffen wird; ''NumBvpLinear'' geht bei linearen Problemen direkt über ein Gleichungssystem. ---- ===== Zusammenspiel ===== * Für **exakte Ableitungen** statt numerischer: [[lyx_-_programmiersprache:units:autodiff|std.autodiff]] liefert sie über Dualzahlen — ''NumNewton'' wird damit sowohl schnell als auch genau. * Die Summationen laufen über [[lyx_-_programmiersprache:units:sum|std.sum]], sind also gegen Auslöschung abgesichert. ---- Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit ''lyxc 1.0.21A'' übersetzt und ausgeführt; Nullstellen, Integrale, Ableitungen und ODE-Lösungen gegen geschlossene Ausdrücke geprüft.