![]() |
Re: Quadratische Gleichungen vollständig lösen
Erster Fehlerbericht: Bug zB für: a=1 b=0 c=1. Mit der beigepackten EEX: -> Meldung "p is a very small nummber" dann "ungültige Gleitkomma-Op", in der D6-IDE x1=NAN x2=NAN.
Die "kleines p"-Logik ist noch ziemlich daneben, denn nach Ausgabe "p is a very small nummber" wird sqrt(0-1) berechnet -> Crash. Erste Abhilfe für den Fall oben: Ersetze if (p>=0) and (p<sqrt(Math.MinDouble)) then durch if (p>0) and (p<sqrt(Math.MinDouble)) then Ich sehe nicht, warum überhaupt soviel Aufhebes für "kleines p" gemacht wird. Kritisch sind hier eigentlich "nur" die Diskriminantenberechnung (und Overunder/flow). Ein Kriterium, wann die Diskriminante mit erhöhter Genauigkeit berechnet werden muß, findet man zB bei W.Kahan, On the Cost of Floating-Point Computation Without Extra-Precise Arithmetic ![]() Gammatester |
Re: Quadratische Gleichungen vollständig lösen
Ein weiterer Bug und zwei Vorschläge:
Bug: a=1, b=1e140, c=1 ergibt X1= -2,14326739881213E123 X2= -1E140 a wird zwar erst richtig mit Vieta berechnet, dann aber im else-Zweig von 'p<0' überschrieben. Vorschlag 1: Das Demo-Programm ist so geändert, das nur einmal die Gleichung gelöst wird, und nicht 6! (in Worten sechsmal) bei komplexen Lösungen. Vorschlag 2: Der "Small p"-Zweig wurde entfernt, wenn er beigehalten werden soll (wofür keine Notwendigkeit besteht, für kleine negative p wird er zB ja auch nicht benutzt!), muß eine Fallunterscheidung Diskriminante <0 und >=0 eingebaut werden. Hier der vollständige Code.
Delphi-Quellcode:
unit Unit1;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,Math, ComCtrls; type TForm1 = class(TForm) Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Button1: TButton; Label1: TLabel; Edit4: TEdit; Edit5: TEdit; Button2: TButton; RichEdit1: TRichEdit; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; Label7: TLabel; procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button1Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} type MySolution = Record a,b,d:double; c:integer;// 1: 2 real solution; 2: 1 real solution; // 3: 2 complex solutions end; //Wolfgang Mix - Delphi-PRAXiS function SolveQuadraticEquation( a, b, c : Double ): MySolution; var p, q , discriminant, discriminant2, re, im: Double; begin // ax² + bx + c = 0 if (a = 0) then raise Exception.CreateFmt ('a should not be zero, no quadratic equation',[result.a]); p := b / a; q := c / a; //if p is a very big number - sqr(p/2) > MaxDouble if abs(p)>sqrt(Math.MaxDouble) then begin //code showmessage('p is a very big number'); //showmessage(floattostr(Math.MaxDouble)); result.a:=abs(p) + sqrt(0.25 - (q/p)/p); result.b:=abs(p) - sqrt(0.25 - (q/p)/p); result.c:=1; result.d:=0.25 - (q/p)/p; exit; end; // calculate discriminant discriminant := sqr(p/2) - q; Result.d := discriminant; // calculate real value re:=-p/2; // calculate imaginary value im:=sqrt(abs(discriminant)); //Form1.Edit7.Text:=FloatToStr(discriminant); if discriminant > 0 then begin // 2 solutions if p>=0 then begin Result.b := -p/2 - sqrt(discriminant); Result.a := q/Result.b; //x1 mit Vieta Result.c := 1; end else begin Result.a := -p/2 + sqrt( discriminant); Result.b := q/Result.a; //x2 mit Vieta Result.c := 1; end; end else if discriminant < 0 then begin // 2 complex solutions Result.a := re; Result.b := im; Result.c := 3; end else begin // 2 equal solutions Result.a := -p/2; Result.b := Result.a; Result.c := 2; end; end; procedure TForm1.Button1Click(Sender: TObject); var a,b,c,discriminant: double; indicator:integer; qs: MySolution; begin RichEdit1.Lines.Clear; a:=StrToFloat(Edit1.Text); b:=StrToFloat(Edit2.Text); c:=StrToFloat(Edit3.Text); if (a=0) then begin // Don't calculate showmessage ('a should not be zero, no quadratic equation'); sleep(2000); exit; end else begin {WE: Gleichung nur einmal lösen und Ergebnisse anzeigen} qs := SolveQuadraticEquation(a,b,c); indicator := qs.c; case indicator of 1: Begin Label1.Caption:='2 real solutions'; RichEdit1.Lines.Add ('X1= ' + FloatToStr(qs.a)); RichEdit1.Lines.Add ('X2= ' + FloatToStr(qs.b)); End; 2: Begin Label1.Caption:='1 real solution'; RichEdit1.Lines.Add ('X= ' + FloatToStr(qs.a)); end; 3: Begin Label1.Caption:='2 complex solutions'; RichEdit1.Lines.Add ('X1= ' + FloatToStr(qs.a)+ ' + ' + FloatToStr(qs.b)+ ' i '); RichEdit1.Lines.Add ('X2= ' + FloatToStr(qs.a)+ ' - ' + FloatToStr(qs.b )+ ' i '); End; end; discriminant:= qs.d; Edit4.Text:=FloatToStr(discriminant); Edit5.Text:=IntToStr(indicator); end; end; procedure TForm1.Button2Click(Sender: TObject); begin close; end; procedure TForm1.FormCreate(Sender: TObject); begin RichEdit1.Clear; end; end. |
Re: Quadratische Gleichungen vollständig lösen
@gammatester:
Vielen Dank, werde ich so übernehmen. Liebe Grüße Wolfgang [edit]... und bug bei sehr großen Zahlen beseitigt [/Edit]
Delphi-Quellcode:
unit Unit1;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,Math, ComCtrls; type TForm1 = class(TForm) Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Button1: TButton; Label1: TLabel; Edit4: TEdit; Edit5: TEdit; Button2: TButton; RichEdit1: TRichEdit; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; Label7: TLabel; procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button1Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} type MySolution = Record a,b,d:double; c:integer;// 1: 2 real solution; 2: 1 real solution; // 3: 2 complex solutions end; //Wolfgang Mix - Delphi-PRAXiS function SolveQuadraticEquation( a, b, c : Double ): MySolution; var p, q , discriminant, discriminant2, re, im: Double; begin // ax² + bx + c = 0 if (a = 0) then raise Exception.CreateFmt ('a should not be zero, no quadratic equation',[result.a]); p := b / a; q := c / a; //if p is a very big number - sqr(p/2) > MaxDouble if abs(p)>sqrt(Math.MaxDouble) then begin showmessage('p is a very big number'); result.d := 0.25 - (q/p)/p; im := abs(p)*sqrt(abs(result.d)); if result.d>0 then begin result.a := -p/2 - sign(p)*im; result.b := q/result.a; result.c := 1; end else if result.d<0 then begin Result.a := -p/2; Result.b := im; Result.c := 3; end else begin Result.a := -p/2; Result.b := Result.a; Result.c := 2; end; exit; end; // calculate discriminant discriminant := sqr(p/2) - q; Result.d := discriminant; // calculate real value re:=-p/2; // calculate imaginary value im:=sqrt(abs(discriminant)); //Form1.Edit7.Text:=FloatToStr(discriminant); if discriminant > 0 then begin // 2 solutions if p>=0 then begin Result.b := -p/2 - sqrt(discriminant); Result.a := q/Result.b; //x1 mit Vieta Result.c := 1; end else begin Result.a := -p/2 + sqrt( discriminant); Result.b := q/Result.a; //x2 mit Vieta Result.c := 1; end; end else if discriminant < 0 then begin // 2 complex solutions Result.a := re; Result.b := im; Result.c := 3; end else begin // 2 equal solutions Result.a := -p/2; Result.b := Result.a; Result.c := 2; end; end; procedure TForm1.Button1Click(Sender: TObject); var a,b,c,discriminant: double; indicator:integer; qs: MySolution; begin RichEdit1.Lines.Clear; a:=StrToFloat(Edit1.Text); b:=StrToFloat(Edit2.Text); c:=StrToFloat(Edit3.Text); if (a=0) then begin // Don't calculate showmessage ('a should not be zero, no quadratic equation'); sleep(2000); exit; end else begin {WE: Gleichung nur einmal lösen und Ergebnisse anzeigen} qs := SolveQuadraticEquation(a,b,c); indicator := qs.c; case indicator of 1: Begin Label1.Caption:='2 real solutions'; RichEdit1.Lines.Add ('X1= ' + FloatToStr(qs.a)); RichEdit1.Lines.Add ('X2= ' + FloatToStr(qs.b)); End; 2: Begin Label1.Caption:='1 real solution'; RichEdit1.Lines.Add ('X= ' + FloatToStr(qs.a)); end; 3: Begin Label1.Caption:='2 complex solutions'; RichEdit1.Lines.Add ('X1= ' + FloatToStr(qs.a)+ ' + ' + FloatToStr(qs.b)+ ' i '); RichEdit1.Lines.Add ('X2= ' + FloatToStr(qs.a)+ ' - ' + FloatToStr(qs.b )+ ' i '); End; end; discriminant:= qs.d; Edit4.Text:=FloatToStr(discriminant); Edit5.Text:=IntToStr(indicator); end; end; procedure TForm1.Button2Click(Sender: TObject); begin close; end; procedure TForm1.FormCreate(Sender: TObject); begin RichEdit1.Clear; end; end. |
Re: Quadratische Gleichungen vollständig lösen
Ich würde mir wünschen, wen der Code einige Grundregeln sauberer Programmierung umsetzen würde:
1. Bezeichner sind nicht selbsterklärend. ('Im', 'Re', Feldbezeichner '.indicator', '.a'). Die Parameter a,b,c sind ok, denn das ist einschlägig bekannt. 2. Code-Styleguides werden ignoriert (Typen fangen z.B. in Delphi mit 'T' an) 3. Magic Numbers (case indicator of) 4. DRY-Prinzip ignoriert (mehrfache Verwendung fast identischer Faktoren/Formeln). 5. Verwendung englischer Bezeichner in einem deutschne Beispielcode. 5. Verwendung englischer Kommentare in einem deutschen Beispielcode. Weiterhin habe ich als Laie nicht begriffen, wo die Vorteile dieser sehr komplexen Lösung sind. Ich hätte gern ein paar numerische Beispiele, um die Vorteile zu erkennen. Ich kann mir das PDF ja durchlesen, aber einige Beispielrechnungen, anhand derer man die Klimmzüge nachvollziehen kann, wären für ein Tutorial und das Verständnis sehr hilfreich. Als Laie kann ich mir nämlich vorstellen, das das Problem der ziemlich großen Zahlen durch verwendung eines Extended-Zwischenresultats vermieden werden könnte (ich liege das sicherlich falsch, aber bis zur Behauptung des Gegenteils...). Hier der naive Vorschlag mit dem Versuch 1-5 zu vermeiden. Der Code sollte selbsterklärend sein (ist aber ungetestet):
Delphi-Quellcode:
Wenn man den numerisch sicherlich stabileren Code von Wolfgang hinsichtlich der Nomenklatur anpasst, könnte man beide Verfahren nehmen, um anhand von Beispielen die Überlegenheit des hier vorgestellten Codes zu verdeutlichen.
Type
TQuadratischeGleichungLoesungstyp = (qlEineLoesung, qlZweiLoesungen, qlKomplexeLoesungen); TLoesungEinerQuadratischenGleichung = Record LoesungsTyp : TQuadratischeGleichungLoesungstyp; Loesung1, Loesung2 : Double; end; Function LoeseQuadratischeGleichung (a,b,c : Double) : TLoesungEinerQuadratischenGleichung; Var basis, diskriminante, offset : Extended; begin If IsZero(a) Then Raise EDivByZero.Create('Es wurde ein ungültiger Parameter angegeben (a darf nicht null sein)'); basis := -b / (2*a); diskriminante := sqr(b) - 4*a*c; offset := sqrt(abs(diskriminante)) / (2*a); if diskriminante<0 then begin result.LoesungsTyp := qlKomplexeLoesungen; result.Loesung1 := basis; result.Loesung2 := offset; end else if diskriminante>0 then begin result.LoesungsTyp := qlZweiLoesungen; result.Loesung1 := basis - offset; result.Loesung2 := basis + offset; end else begin result.LoesungsTyp := qlEineLoesung; result.Loesung1 := basis; result.Loesung2 := result.Loesung1; end; end; Das wäre dann ein Paradebeispiel für mathematisch exakte Programmierung, und das sie nicht trivial ist. [edit]'determinante' durch 'diskriminante' ersetzt, dank W.Mix[/edit] |
Re: Quadratische Gleichungen vollständig lösen
@alzaimar: Mit Deinem Vorschlag wären wir wieder am Asugangspunkt. Zwar verständlicher und hübscher, aber keinen Deut besser. Wenn nur die pq-Formel gut umgesetzt werden soll in mathematisch exakte Programmierung, trifft jfheins' Argument aus #19 wieder zu. Ich meine, daß ein Codelib-Beitrag doch mehr bringen sollte.
Dein Vorschlag ignoriert sämtliche angesprochen numerischen Probleme. Selbst der Hinweis auf extended zieht nicht, da die Diskriminante (nicht Determinante!) sqr(b) - 4*a*c eh schon extended gerechnet und dann in double gespeichert wird. Auch tragen die von Dir gewählten Bezeichnungen Basis und Offset nicht zum Verständnis bei und sorgen eher für Verwirrung denn Klarheit. Nix für ungut Gammatester |
Re: Quadratische Gleichungen vollständig lösen
Ich persönlich finde es übrigens eher unpraktisch, dass das Ergebnis "kodiert" ist (man muss also den Lösungstypen auswerten und eine case-Unterscheidung machen, um überhaupt etwas mit dem Ergebnis anfangen zu können ...)
Schöner wäre es, stattdessen 2 kurze Funktionen bereitzustellen: Zum Beispiel eine Funktion die immer 2 komplexwertige Nullstellen liefert und eine andere (für die, die keine komplexen Zahlen mögen/brauchen) die 2 double-Werte zurückliefert, von denen dann einer oder beide NaN sein können (falls es nur eine oder keine reelle Nullstelle gibt) |
Re: Quadratische Gleichungen vollständig lösen
@alzaimar
Zuerst einmal: Ich betrachte mich nicht als Profiinformatiker sondern als vorangeschrittener Anfänger, zu mindestens in Delphi, mit dem ich mich erst 1 Jahr versuche. Die wissenschaftliche Weltsprache ist nun einmal Englisch. Folgt man Deinem Vorschlag, sollte dann konsequenterweise auch die gesamte Pascalsyntax eingedeutscht werden Ich versuche, mich weitgehend an das Styleguide von Embarcadero zu halten. Selbst das von Delhi-Treff nach deutsch übersetzte Styleguide verwendet englische Kommentare und Bezeichner. ![]() Ein weiterer Grund für mich, diesen Stil beizubehalten ist, daß hier nicht nur deutsches Publkum liest. @gammatester Danke für das hervorragende Scriptum von Prof. W. Kahan, das ich mir nebenbei auch noch zu Gemüte führen werde. Leider habe ich kein Mathlab, wäre deswegen aber dankbar für zu prüfende Koeffitienten a,b und c mit demonstrativ ungenauen und genaueren Ergebnissen. @jfheins: Im späteren Script werde ich schrittweise mit noch mehr Erklärungen an diesen Brocken heranführen und Deinen Vorschlag mit übernehmen. Ich merke langsam, das Tutorial wird zeitaufwendiger als ich dachte, macht aber nix :) |
Re: Quadratische Gleichungen vollständig lösen
Zitat:
Mit a=10^n, b=-2*10^n+2, c=10^n+2, also zB a=1000, b=-2002, c=1002. Mit den Deinen Bezeichnungen sind die Lösungen X1=1, X2=1+2/10^n und die Diskriminante D=1/10^(2n), und dies sind die Rechenergebnisse (erste Spalte ist n):
Code:
Sie zeigen auch schön, daß die Berechnung der Diskriminante der kritische Teil ist (mit der nicht gekürzten Formal ist sie immer b^2-4ac = 4!), und daß der Fehler in den Lösungen langsam nach "vorn kriecht" und ab n=8 reicht die genauigkeit nicht mehr aus.
1 X1=1,2 X2=0,999999999999999 D=0,0100000000000002
2 X1=1,02 X2=1 D=0,0001 3 X1=1,00199999999989 X2=1,00000000000011 D=9,99999999777763E-7 4 X1=1,0002 X2=1 D=1,0000000000049E-8 5 X1=1,0000200000111 X2=0,999999999988896 D=1,00000222078706E-10 6 X1=1,00000199988897 X2=1,00000000011103 D=9,99777951399272E-13 7 X1=1,00000020110428 X2=0,999999998895723 D=1,02220749226278E-14 8 X1=1,00000001 + 1,10490345560989E-8 i X2=1,00000001 - 1,10490345560989E-8 i D=-1,22081164621868E-16 Ich meine, daß im Rahmen der Delphi-Praxis und als Basis für ein Tutorial Deine jetzige Berechnungsroutine ausreicht. Wie schon von anderen gesagt, finde ich allerdings auch mM das Interface (also die Parameterübergabe) verbesserungswürdig. In dem von Dir angehängten PDF wird zB vorgegeben:
Delphi-Quellcode:
Ich verwende in meiner QuadSolv-Unit folgende Funktionen:
procedure SolveQuadraticEquation(var Anzahl: integer; var IstKomplex: boolean; var x1,x2: double);
Delphi-Quellcode:
function squad(a,b,c: double; var x1,y1,x2,y2: double): integer;
{-Solve the quadratic equation a*x^2 + b*x + c = 0. Result is the number} { of different solutions: 0 (if a=b=0), 1 (x1), or 2 (x1,x2). If the} { result is = -2, x1+i*y1 and x2+i*y2 are the two complex solutions.} { No precautions against over/underflow, NAN/INF coefficients return 0.} function squadx(a,b,c: double; var x1,y1,x2,y2: double): integer; {-Solve the quadratic equation a*x^2 + b*x + c = 0. Result is the number} { of different solutions: 0 (if a=b=0 or INF/NAN), 1 (x1), or 2 (x1,x2).} { If the result is = -2, then x1 + i*y1 and x2 + i*y2 are the two complex} { solutions. Uses scaling by powers of two to minimize over/underflows.} function squad_selftest: integer; {-Selftest for squad core routine, result=0 if OK, else number of the} { failing test case.} |
Re: Quadratische Gleichungen vollständig lösen
@gammatester:
Danke für die Test-Formel, die ist okay, aber wie kommt man auf sowas :?: Zitat:
|
Re: Quadratische Gleichungen vollständig lösen
Zitat:
Zitat:
Zitat:
![]() Zitat:
Ich dachte, es wird ein Tutorial und ein Tutorial würde in einer bestimmten Sprache geschrieben werden. Ich dächte, das wäre dann Deutsch. Das Tutorial würde sich an eher Mathematikinteressierte richten, und weniger an Softwareentwickler. Mir erscheint es nur konsequent, das deutsche Bezeichner und deutsche Kommentare in einem deutschen Tutorial für deutsche Mathematiker angebracht wären. Anders herum machst Du deinem Namen alle Ehre, wenn du die Sprachen Mix't :zwinker: Zitat:
Zitat:
Ich schrieb also als 'Laie', der einen 'naiven Gegenvorschlag' macht, und weiß, das er damit 'sicherlich falsch' liegt. Klonk? Man könnte meinen oder einen ähnlichen Code in das Tutoral einbringen, um anhand konkreter Beispiele zu zeigen, das die scheinbar triviale Lösung (von mir) ungenau ist. Mein Code sollte nur zeigen, das man es 'hübscher' hinbekommen kann. Zitat:
Es gibt übrigens im Context der 'sauberen Programmierung' bei der Ausformulierung von Formeln auch das Bestreben, diese ohne Zwischenresultate zu kodieren (wenn es denn verständlich ist). Dies könnte hier durchaus geschehen. Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:20 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz