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

user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:
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.

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
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)
user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:

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

user profile iconGuaAck hat folgendes geschrieben Zum zitierten Posting springen:
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: