Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   SetRoundMode wirkt nicht immer (https://www.delphipraxis.net/214826-setroundmode-wirkt-nicht-immer.html)

Jasocul 15. Mär 2024 10:40

Delphi-Version: 11 Alexandria

SetRoundMode wirkt nicht immer
 
Kleines Gimmick zum Wochenende.
Da ich gerade ein größeres Refactoring mache, wollte ich alte (etwas merkwürdige) Berechnungen mit Rundungen überarbeiten.
Dabei hatte ich Source, der etwa so aussah:
Delphi-Quellcode:
  Result := Result * Faktor;
  SetRoundMode(rmUp);
  Result := SimpleRoundTo(Result); // Oder war es RoundTo(Result, -2) ?
  SetRoundMode(rmNearest);
Ergebnis: Die Rundung hat nicht funktioniert, wie es vorgesehen war.
Dann wie folgt geändert:
Delphi-Quellcode:
  SetRoundMode(rmUp);
  Result := Result * Faktor;
  Result := SimpleRoundTo(Result);
  SetRoundMode(rmNearest);
Ergebnis: Jetzt funktioniert es.
Es scheint so, als müssten die verwendeten Variablen im gesamten Kontext den RoundMode haben. Ich habe das nicht weiter geprüft und im Endeffekt verwerfen müssen. Der Code ist also auch aus dem Kopf geschrieben.
Ich bin auf die Lösung gekommen, als ich mit dem Debugger nach der Rundung nochmal in die Berechnnungszeile gegangen bin und die Schritte wiederholt habe. Ich dachte erst, dass die Rundungsfunktion doppelt durchlaufen werden müsste (manchmal macht Delphi schon merkürdige Dinge). Aber es war halt die andere Lösung des Problems.

Leider bin ich am WE nicht online. Vielleicht ist das nur bei mir so. Aber vielleicht hat jemand Lust, das selbst mal zu testen. Dann wüsste ich zumindest, ob es an Delphi oder mir liegt.

Andreas13 15. Mär 2024 11:04

AW: SetRoundMode wirkt nicht immer
 
Hallo Peter,
es kommt bereits in der ersten Zeile des ersten Codes
Delphi-Quellcode:
Result := Result * Faktor;
bei der Multiplikation zu einer Rundung. Wenn der Rundungsmodus zuvor ein anderer war als der von Dir gewünschte, dann wurde nach diesem gerundet.

PS:
Mit
Delphi-Quellcode:
GetRoundMode
http://docwiki.embarcadero.com/Libra...h.GetRoundMode kannst Du den alten (= "falschen") Rundungsmodus ermitteln.

Jasocul 15. Mär 2024 11:24

AW: SetRoundMode wirkt nicht immer
 
Wieso sollte es in der ersten Zeile zu einer Rundung kommen? Es wird dort keine Rundungsfunktion aufgerufen.
Der Roundmode war übrigens rmNearest.

Andreas13 15. Mär 2024 12:53

AW: SetRoundMode wirkt nicht immer
 
Tja, das sind die Tücken der Realzahl-Arithmetik (floating point operations)… :( Hier wird immer gerundet, von ganz-ganz-ganz-ganz seltenen Ausnahmen abgesehen. Das kommt vom Grundproblem der binären Darstellung von reellen Zahlen mit einer endlichen Anzahl von Bits:
Denn selbst zwischen zwei beliebig dicht beieinander liegenden reellen Zahlen existieren immer unendlich viele weitere reelle Zahlen. Noch schlimmer, ihre Menge ist sogar mächtiger (= „mehr als unendlich“): nämlich überabzählbar (s. z. B. https://de.wikipedia.org/wiki/Unendliche_Menge).

Kurz und gut: Für die binäre Codierung von unendlich vielen Zahlen benutzt der Computer lediglich eine endliche Anzahl von Bits:
z.B.
Real: 6 Bytes (= 48 Bits)
Double: 8 Bytes (= 64 Bits)
Extended: 10 Bytes (= 80 Bits) etc.

Selbst Typen von Multipräzisions-Arithmetik (MPA) haben eine endliche Bitlänge: z.B. 120, 360, 1024 Bits etc.

Fazit:
Der Computer kann reelle Zahlen nicht exakt darstellen! Selbst bei der Zuweisung wie z. B.
Delphi-Quellcode:
Zahl:= 0.1;
wird gerundet, weil das binäre Äquivalent von 0.1 unendlich viele Bits hat.
Also rundet der Prozessor immer!

Die Anweisung
Delphi-Quellcode:
SetRoundMode(..);
ist gar nicht für die Benutzer gedacht, denn dies beeinflußt die gesamte Arbeitsweise des Prozessors. Ich würde die Standardeinstellung rmNearest behalten, sonst können gewohnte mathematische Rundungsregeln außer Kraft gesetzt werden und unerwartete Resultate liefern.

Runden solltest Du selber ganz am Ende aller Berechnungen mittels
Delphi-Quellcode:
Math.SimpleRoundTo
,
Delphi-Quellcode:
System.Round
oder
Delphi-Quellcode:
System.Math.RoundTo
auf die gewünschte Stellenzahl. Wenn manchmal ein unschönes Ergebnis wie 1.999999999999999999 rauskommt, anstelle von 2.000000000000000000, liegt es nicht an Dir. :-D
Da helfen nur mehr, vieeeeeeeeel mehr Stellen, also mindestens Extended anstelle von Double, wenn Du auf Double runden willst. Oft genügt das auch nicht.
Ich benutze MPA-Routinen mit 100 .. 200 Stellen, die ich dann auf Extended (18..19 Stellen) oder Double (14..15 Stellen) runde: Dann ist der kleine Schönheitsfehler (mit groooßem Aufwand) beseitigt...:-D

Jasocul 15. Mär 2024 13:40

AW: SetRoundMode wirkt nicht immer
 
Das würde bedeuten, dass Delphi intern auch den Roundmode benutzt, wenn ohne Rundungsfunktion gerechnet wird. Das halte ich für unwahrscheinlich. Ansonsten ist mir das Thema Gleitzahlarythmetik bekannt. Ist zwar lange her, aber ein paar Semester Mathe-Studium habe ich hinter mir.:wink:

Andreas13 15. Mär 2024 14:31

AW: SetRoundMode wirkt nicht immer
 
Sorry Peter,
Zitat:

Zitat von Jasocul (Beitrag 1534701)
Das würde bedeuten, dass Delphi intern auch den Roundmode benutzt, wenn ohne Rundungsfunktion gerechnet wird.

aber das verstehe ich nicht ganz. :oops:
Könntest Du das bitte etwas verdeutlichen?

himitsu 15. Mär 2024 14:50

AW: SetRoundMode wirkt nicht immer
 
Die Delphi-"Funktionen", arbeiten fast alle mit der FPU und dieser Modus wird in der FPU eingestellt (ich glaub nur für den aktuellen Thread).
Und Jupp, ganzzahlig (Integer) wird in der CPU berechnet, nicht in der FPU, also in den normalen Registern ala EAX anstatt ST0.

Somit hat ein Umstellen, innerhalb des Hauptthreads, auch Einfluß auf alles Mögliche, wie z.B. FireMonkey.

Jasocul 15. Mär 2024 17:54

AW: SetRoundMode wirkt nicht immer
 
Zitat:

Zitat von himitsu (Beitrag 1534704)
Die Delphi-"Funktionen", arbeiten fast alle mit der FPU und dieser Modus wird in der FPU eingestellt (ich glaub nur für den aktuellen Thread).
Und Jupp, ganzzahlig (Integer) wird in der CPU berechnet, nicht in der FPU, also in den normalen Registern ala EAX anstatt ST0.

Somit hat ein Umstellen, innerhalb des Hauptthreads, auch Einfluß auf alles Mögliche, wie z.B. FireMonkey.

Roundmode ist threadsafe. Deine Vermutung trifft da zu.
Die Einstellung des Mode wirkt sich auf die FPU aus, wenn ich die DOH richtig verstanden habe. Allerdings bin ich davon ausgegangen, dass sich das nur auswirkt, wenn Rundungsfunktion aufgerufen werden. Das Verhalten, das ich festgestellt habe, würde bedeuten, dass es sich auf jede Rechenoperation in der FPU auswirkt.

Das wäre für mich jetzt nachvollziehbar, aber da könnte der Hinweis in der Hilfe deutlicher sein, dass von der Einstellung nicht nur die Rundungsfunktionen in Delphi betroffen sind.

himitsu 15. Mär 2024 18:42

AW: SetRoundMode wirkt nicht immer
 
Floats sind immer etwas gerundet, da sie keine exakten irrationalen Werte speichern können,
vor allem beim Übergang von Extended/Double/Single in Zwischenberechnungen.

Die genaue Funktion wirst du wohl beim Bei Google suchenFLDCW oder Delphi-Referenz durchsuchenSet8087CW finden, bzw. beim Bei Google suchenLDMXCSR/Delphi-Referenz durchsuchenSetMXCSR für SSE (Delphi-Referenz durchsuchenSetSSERoundMode).
Und zusätzlich noch Delphi-Referenz durchsuchenSetPrecisionMode (ebenfalls FLDCW/Set8087CW)




https://docwiki.embarcadero.com/RADS...int_Arithmetic
https://docwiki.embarcadero.com/RADS...elphi_for_x64)


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:33 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz