Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Terme umformen für Stabilität von * und /
Hidden - Fr 12.07.13 09:18
Titel: Terme umformen für Stabilität von * und /
Hallo,
In einem meiner Projekte habe ich folgende Zuweisung:
Delphi-Quelltext
1:
| x := Trunc( (locX + shift) * FieldW * BufW / ( w * FullW * scale ) ); |
scale ist vom Typ Extended, ansonsten sind alles Integers.
Um das unten beschriebene Problem zu umgehen, könnte ich die hintere Klammer auflösen und die entstehenden Divisionen ggf. etwas nach vorne ziehen (vielleicht auch die vordere Klammer
(locX + shift) ausmultiplizieren). Wie gehe ich dabei vor, damit der Wert für
x möglichst Stabil ist, d.h. damit er für möglichst viele Variablenbelegungen möglichst nahe am richtigen Ergebnis liegt?
Ich nehme an, dass für Überlauf-Stabilität die Zwischenergebnisse möglichst nahe an 1 liegen sollten (Exponent), ich für Genauigkeit aber möglichst viele der Zwischenergebnisse in Integer-Typen halten will (Mantisse), korrekt?
Zwischenergebnisse nahe an 1 halten:
1:
| x := Trunc( (locX + shift) * ( FieldW / FullW ) * ( BufW / w ) / scale ) ); |
Lange in Integer bleiben:
1:
| x := Trunc( ( (locX + shift) * FieldW * BufW ) / ( ( w * FullW ) * scale ) ); |
Auch wird
hier [
http://www.delphipages.com/forum/showthread.php?t=184982] spekuliert, dass die Zwischenergebnisse eine reduzierte Genauigkeit haben (statt einer erhöhten :shock:)
Zum Effekt:
Obwohl alle Variablen positiv sind, ist das Ergebnis negativ
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| ( x = -144 für
locX = 507 shift = 4578 FieldW = 300 BufW = 1408 w = 1408 FullW = 1500 scale = 7,05219818923083 ) |
Wenn ich diese Werte im Quelltext für die Variablen einsetze (
x := Trunc( (507 + 4578) * 300 * 1408 / ( 1408 * 1500 * 7.05219818923083 ) );, erhalte ich vom Compiler:
Quelltext
1:
| [DCC Error] frmMain.pas(494): E2099 Overflow in conversion or arithmetic operation |
lg,
Daniel
Gammatester - Fr 12.07.13 10:02
Hidden hat folgendes geschrieben : |
9818923083 )[/code]
Wenn ich diese Werte im Quelltext für die Variablen einsetze (x := Trunc( (507 + 4578) * 300 * 1408 / ( 1408 * 1500 * 7.05219818923083 ) );, erhalte ich vom Compiler:
Quelltext 1:
| [DCC Error] frmMain.pas(494): E2099 Overflow in conversion or arithmetic operation | |
Dein Compiler hat ja auch völlig recht, denn
(507 + 4578) * 300 * 1408 = 2147904000 > MaxLongint. Dieser Ausdruck wird ja in Integer-Arithmetik berechnet und führt zum Überlauf. Bei diesen mikrigen Zahlen (und ohne Subtraktionen) braucht man sich nicht um Termumformungen und Statibilität zusorgen, wenn man die richtigen Datenrepresentationen verwendet: Entweder eine Deiner Konstanten als Float schreiben, zB 1408.0, oder eine temporäre extended Variable verwenden.
Delphi-Quelltext
1: 2:
| tmp := locX + shift; x := trunc( tmp * FieldW * BufW / ( w * FullW * scale )); |
wobei eventuell je nach Datentypen und Wertebereich auch w * FullW * scale überlaufen kann, am sichersten wäre dann etwas wie
Delphi-Quelltext
1: 2: 3:
| tmp := locX + shift; tmp := tmp * FieldW * BufW / w / FullW / scale; x := trunc(tmp); |
Hidden - Fr 12.07.13 10:15
Der volle Divisor (also w * FullW * scale) sollte nicht überlaufen, besonders da scale bereits extended ist.
Gammatester hat folgendes geschrieben : |
Edit: Mist :( - Statt edit auf Antwort geklickt, kann mit vorherigem Beitrag verschmolzen werden. |
:welcome: habe ebenfalls 1 Minute nach Thread-Erstellung den Komplett-Quote zwecks Löschen gemeldet, den ich statt eines Edits gemacht hatte :lol:
Edit: Das hier ist, wie man es wahrscheinlich in den meißten Fällen machen sollte (iirc)
Hidden hat folgendes geschrieben : |
Zwischenergebnisse nahe an 1 halten: 1:
| x := Trunc( (locX + shift) * FieldW / FullW * BufW / w / scale ) ); | |
Martok - Fr 12.07.13 11:38
Langweilige Info zuerst: wenn man alles in Extended rechnet, funktionierts.
In Delphi bestimmt immer der erste Ausdruck einer Kette, in welchen Typ der Compiler das zusammenfasst. Hier also die 507. Was hilft:
Delphi-Quelltext
1:
| x := Trunc( (507.0 + 4578) * 300 * 1408 / ( 1408 * 1500 * 7.05219818923083 ) ); |
Mit Variablen wäre z.b. das eine Lösung:
Delphi-Quelltext
1:
| x:= Trunc((1.0 * locX + FXShift) * FieldWidth * BufW / (w * FullW * FZoomFact)); |
Die 1.0 erzwingt dabei Fließkommaarithmetik, alles ab da passiert auf der FPU.
jaenicke - Fr 12.07.13 12:52
Oder einfach so:
Delphi-Quelltext
1:
| x := Trunc( Int64(507 + 4578) * 300 * 1408 / ( 1408 * 1500 * 7.05219818923083 ) ); |
Es sollte auch reichen Int64 statt Integer für die Variablen zu nutzen.
Vorausgesetzt der Maximalwert von Int64 wird nicht überschritten, dann geht das natürlich so auch nicht.
GuaAck - Fr 12.07.13 19:19
Dda gibt es noch einen anderen Aspekt:
Die Reihenfolge der Berechnung und auch der "Zeitpunkt" der Typenumwandlung können von der Compilerversion und vielleicht sogar von Optimierungseinstellungen abhängen.
Wenn es also wirklich auf reproduzierbare Ergebnisse ankommt, dann sollte man alles selbst steuern, z. B. wie vorgeschlagen über Zwischenvariable. Diese Zwischenvariable würde ich sogar z. B. im PUBLIC-Teil der Klasse definieren, so dass der Compiler sie auf jeden Fall anlegen und berechnen muss. Evtl. kann man das auch mit einem Compiler-Schalter für den Code-Bereich erzwingen, dass weiß ich aber nicht.
Gruß
GuaAck
Hidden - Fr 12.07.13 20:01
GuaAck hat folgendes geschrieben : |
Diese Zwischenvariable würde ich sogar z. B. im PUBLIC-Teil der Klasse definieren, so dass der Compiler sie auf jeden Fall anlegen und berechnen muss. Evtl. kann man das auch mit einem Compiler-Schalter für den Code-Bereich erzwingen, dass weiß ich aber nicht. |
Hmm, da wäre mir lieber dass andere sich beim Lesen des Programms und Verstehen der public-Variablen nicht wundern müssen.
Wenn es so kritisch wird, schaltet man vielleicht lieber Compier-Optimierungen für diese Quelltextstelle ganz aus.
Ich bin ja so wie so der Meinung, dass die kürzeste Lösung die leicht verständlichste und beste ist. Und Zwischenvariable deklarieren plus zuweisen sind schon wieder zwei Zeilen mehr*, wobei man wahrscheinlich mit einem expliziten Cast das selbe Ergebnis erhält(?).
lg,
Daniel
*Es sei denn, es erhöht bei einer langen Rechnung das Verständnis, z.B. durch mehr Übersicht oder Benennung eines Zwischenergebnisses.
OlafSt - Fr 12.07.13 22:35
Die Anzahl Zeilen im Sorcecode ist doch heutzutage mehr als nur irrelevant ;)
Ich würds "ausführlich" - und damit richtig - programmieren und nen Kommentar dazusetzen, wieso ich so schwafelig bin :lol:
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!