Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Rundungs-Probleme bei Ausgabe als String (https://www.delphipraxis.net/195265-rundungs-probleme-bei-ausgabe-als-string.html)

raller09 15. Feb 2018 09:39

Rundungs-Probleme bei Ausgabe als String
 
Moin,

ich habe seit langen mal wieder ein Problem mit der Rundung.
Die grundsätzlichen Probleme mit der Darstellung von Fließkomma-Werten in binärer Form sind mir klar. Auch die interne Datenhaltung verstehe isch (einigermaßen :roll:).
Wenn mir alles klar wäre, würde ich jetzt vermutlich aber nicht fragen:

Ich habe einen Wert aus einer Datenbank, der in eine Delphi-Double-Variable kopiert wird.
Dieser Wert soll dann in einem String ausgegeben werden. Dabei sollen fix 5 Nachkommastellen in der String-Ausgabe genutzt werden.

Delphi-Quellcode:
function Test: string;
var
  lWert: Double;
  lAnzahlNachkommaStellen: integer;
begin
  Result := '';

  lAnzahlNachkommaStellen := 5;
  lWert := 13.727735;

  Result := Format( '%.*f',
                    [ lAnzahlNachkommaStellen,
                      lWert]);
end;

Leider ist es bei mir so, das dabei bei unterschiedlichen Aufrufen unterschiedliche Werte zurückgeliefert werden.
Beim ersten Aufruf wird bei mir aktuell reproduzierbar "13.72773" ausgegeben, bei den folgenden "13.72774". Dazwischen habe ich mit dem Programm einiges gemacht.

Ich habe auch schon einige der anderen Umwandlungen genutzt:
FloatToStr( ...),
FloatToStrF( ...);

diese verhalten sich anscheinend identisch.


Ich denke, in meinem Fall ist es nicht das "Bankers Rounding", dass mir die Werte verstellt. Da müsste dann ja immer der gleiche Wert herauskommen. Auch sage ich nicht, welcher der beiden Werte der richtige sein muss (das hängt ja von der Rundung ab...). Ich benötige nur reproduzierbar gleiche Werte.


Ich habe dann zum testen folgende Funktion probiert:
kaufmännisch runden

Delphi-Quellcode:
  Result := Format( '%.*f',
                    [ RoundX( lWert,
                              lAnzahlNachkommaStellen)]);
Diese verhindert aber das Unterschiedliche Ergebnis bei mehreren Aufrufen nicht.

Erst wenn ich die Rundungs-Funktion mit "lAnzahlNachkommaStellen + 1" aufrufe, scheint es so, dass ich ein reproduzierbares Ergebnis bekomme...
Ist das die korrekte Lösung?
Oder stehe ich ganz woanders auf dem Schlauch...

Zusätzlich: Entspricht die Funktion RoundX aus der Library nicht auch der Funktion "SimpleRoundTo" aus Delphi (bei mir Berlin 10.1 Up2), wenn für "RoundUp" die "Alternative Implementation" genutzt wird? Kann mir jemand den Unterschied erklären? Was ist hier der Unterschied zwischen "Int" und "Trunc", hat die Reihenfolge von "/" und "*" Einfluss auf das Ergebnis?


Vielen Dank

Uwe Raabe 15. Feb 2018 10:26

AW: Rundungs-Probleme bei Ausgabe als String
 
Zitat:

Zitat von raller09 (Beitrag 1393910)
Leider ist es bei mir so, das dabei bei unterschiedlichen Aufrufen unterschiedliche Werte zurückgeliefert werden.
Beim ersten Aufruf wird bei mir aktuell reproduzierbar "13.72773" ausgegeben, bei den folgenden "13.72774". Dazwischen habe ich mit dem Programm einiges gemacht.

Dann zeig doch mal alle Manipulationen an dieser Variablen von der ersten Zuweisung über die Anzeige mit dem ersten Wert bis zu dem Zeitpunkt, wo der andere Wert angezeigt wird.

raller09 15. Feb 2018 10:42

AW: Rundungs-Probleme bei Ausgabe als String
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1393911)
Dann zeig doch mal alle Manipulationen an dieser Variablen von der ersten Zuweisung über die Anzeige mit dem ersten Wert bis zu dem Zeitpunkt, wo der andere Wert angezeigt wird.


Das ist eine neue Variable. zwischendurch alles freigegeben. Längerer Ablauf. die Variable ist Teil eines TDictionary< Integer, Double>.
Der Weg zu der Variable ist aber komplett identisch... Es wird nach der Zuweisung nur Lesend darauf zugegriffen.
Es jeweils von mir der selbe Programm-Ablauf angesprochen (Test-Button auf dem Haupt-Formular), wodurch die Daten gelesen werden.


Das MouseOver in Delphi über die Variable zeigt immer "13,727735".
ein "Frac( lWert)" in der Rundungs-Funktion zeigt bei der Fehlersuche -> Überwachte Ausdrücke beim ersten Durchlauf "0,499999999913257", bei den folgenden "0,5".
Somit ist für Delphi die Zahl < ,5 -> also beim ersten Durchlauf immer abrunden.


Ich hab schon bei mir nach SetRoundMode u. ä. gesucht aber nichts gefunden. nachdem ich den String erstellt habe,m wird auch eine .dll genutzt, die den erstellten String verarbeitet.

Selbst meine kleine Test-Funktion aus meinem letzten Post verhält auch so. zuerst "13,72773", nach dem Aufruf meiner Funktion ""13,72774".
Vielleicht ist das der Hinweis, dass da doch was verstellt wird?!?

Fritzew 15. Feb 2018 10:58

AW: Rundungs-Probleme bei Ausgabe als String
 
Zitat:

Zitat von raller09 (Beitrag 1393912)
Ich hab schon bei mir nach SetRoundMode u. ä. gesucht aber nichts gefunden. nachdem ich den String erstellt habe,m wird auch eine .dll genutzt, die den erstellten String verarbeitet.

Was ist das für eine Dll?

Einfach aus Erfahrung: Nach dem Aufruf einer Fremdle kannst Du nie davon ausgehen das
die FPU noch den gewünschten State hat. Deshalb for dem Aufruf mit
GetFPURoundMode sichern und nachher mit SetFPURoundMode() wieder setzen.

freimatz 15. Feb 2018 11:14

AW: Rundungs-Probleme bei Ausgabe als String
 
Schlage vor du machst mal ein ganz kleines Testprogramm dafür.

raller09 15. Feb 2018 13:41

AW: Rundungs-Probleme bei Ausgabe als String
 
Zitat:

Zitat von Fritzew (Beitrag 1393915)
Was ist das für eine Dll?

Einfach aus Erfahrung: Nach dem Aufruf einer Fremdle kannst Du nie davon ausgehen das
die FPU noch den gewünschten State hat. Deshalb for dem Aufruf mit
GetFPURoundMode sichern und nachher mit SetFPURoundMode() wieder setzen.

Das ist ein sehr guter Hinweis!

Ich nutze lpsolve für eine lineare Optimierung (https://sourceforge.net/projects/lpsolve/).
Das ist eine in C geschriebene .dll.

In der Delphi-Zugriffs-Datei steht:
Code:
 *  Important information
 *  Solver library is compiled for a different Control Word, you should change
 *  the Delphi control Word to avoid foating point operation errors.
 *                _control87     Set8087CW
 *  Visual Studio $9001F    ->  639
 *  GCC          $8001F    ->  895
und in der IDE (Main.pas -> in Delphi geschrieben):
Delphi-Quellcode:
initialization
  set8087cw(895);

end.
Ich habe jetzt meine Kommunikation mit der .dll in folgenden Block gepackt:
Delphi-Quellcode:
  //lRoundingMode := GetRoundMode;
  l8087CW      := Get8087CW;
  try
    {
    Hier der gesammte Zugriff auf die .dll
    }
  finally
    ShowMessage( (*
                 'alt: ' + TRttiEnumerationType.GetName( lRoundingMode) +
                 sLineBreak +
                 'neu: ' + TRttiEnumerationType.GetName( GetRoundMode) +
                 sLineBreak +
                *)
                 'alt: ' + IntToStr( l8087CW) +
                 sLineBreak +
                 'neu: ' + IntToStr( Get8087CW)
                 );

    //SetRoundMode( lRoundingMode);

    Set8087CW( l8087CW);
  end;
Das liefert mir:

1.Aufruf
alt: 4991
neu: 610

folgende Aufrufe:
alt: 4991
neu: 4962

Da ich den Wert mittels "Set8087CW" nach der .dll zurückstelle, passt "meine" Rundung/String-Erstellung, ohne das ich irgendwie zusätzlich runden muss...(hat auch tatsächlich nicht korrekt funktioniert)!


Was mich jetzt aber noch stört: Die .dll hat beim 2ten Aufruf einen anderen Wert gesetzt.
Verändert sich dadurch das Verhalten der .dll? (vermutlich, oder?)
Könnt ihr mir hier weiterhelfen, oder muss ich das mit dem Erstellern der .dll klären?


Darf ich im laufenden Programm Set8087CW nutzen? Was wird dadurch gemacht?


Danke,

Zacherl 15. Feb 2018 15:48

AW: Rundungs-Probleme bei Ausgabe als String
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von raller09 (Beitrag 1393939)
Darf ich im laufenden Programm Set8087CW nutzen? Was wird dadurch gemacht?

Darfst du schon. Das X87 Control Word besteht aus mehreren Bits, die unterschiedliche Bedeutungen haben:
Anhang 48562

Nähere Infos dazu entnimmst du am besten der "Intel Architecture Software Developer's Manual; Vol I: Basic Architecture".

Was RC angeht, sind hier die relevanten Werte:
Code:
Round to nearest even      = $00B
Round down toward infinity = $01B
Round up toward infinity   = $10B
Round toward zero (trunc)  = $11B
Zitat:

Zitat von raller09 (Beitrag 1393939)
Was mich jetzt aber noch stört: Die .dll hat beim 2ten Aufruf einen anderen Wert gesetzt.
Verändert sich dadurch das Verhalten der .dll? (vermutlich, oder?)

Kann gut sein, dass nur die Exception Masks geändert wurden. Ich schaue mal nach .. Edit: Ja genau z.b. zwischen 4991 und 4962 ändern sich nur die Exception Masks.

hoika 15. Feb 2018 18:55

AW: Rundungs-Probleme bei Ausgabe als String
 
Hallo,

Zitat:

Erst wenn ich die Rundungs-Funktion mit "lAnzahlNachkommaStellen + 1" aufrufe, scheint es so, dass ich ein reproduzierbares Ergebnis bekomme...
Das ist genau der richtige Ansatz!

Luckie 15. Feb 2018 19:33

AW: Rundungs-Probleme bei Ausgabe als String
 
Zitat:

Zitat von hoika (Beitrag 1393975)
Hallo,

Zitat:

Erst wenn ich die Rundungs-Funktion mit "lAnzahlNachkommaStellen + 1" aufrufe, scheint es so, dass ich ein reproduzierbares Ergebnis bekomme...
Das ist genau der richtige Ansatz!

Ich wollte gerade sagen. Das kann es nicht sein, dass man für eine solche Funktion der Maßen tief in den Prozessorinstruktionen eingreifen muss. Wenn das der Fall ist, dann macht man irgendwas verkehrt.

raller09 16. Feb 2018 07:40

AW: Rundungs-Probleme bei Ausgabe als String
 
Zitat:

Zitat von Zacherl (Beitrag 1393952)
Darfst du schon. Das X87 Control Word besteht aus mehreren Bits, die unterschiedliche Bedeutungen haben.
Nähere Infos dazu entnimmst du am besten der "Intel Architecture Software Developer's Manual; Vol I: Basic Architecture".

Was RC angeht, sind hier die relevanten Werte:
Code:
Round to nearest even      = $00B
Round down toward infinity = $01B
Round up toward infinity   = $10B
Round toward zero (trunc)  = $11B

Habe gesten nachmittag auch das Dokument noch gefunden. Ist dort recht gut erklärt.

Zitat:

Zitat von Zacherl (Beitrag 1393952)
Kann gut sein, dass nur die Exception Masks geändert wurden. Ich schaue mal nach .. Edit: Ja genau z.b. zwischen 4991 und 4962 ändern sich nur die Exception Masks.

Hier mal die Bit-Schreibweise meiner Werte:
Code:
                                      keine Berücksichtigung
Dec    Hex       C RC  Exception  von mir
4991    137F   0001 0011 0111 1111   Delphi vor erster Start
 610     262           10 0110 0010   nach 1. Aufruf
 610     262           10 0110 0010   folgende Aufrufe

                   I  PC            Schutz mit try ...
Dec    Hex       C RC  Exception  finally
4991    137F   0001 0011 0111 1111   Delphi vor erster Start
 610     262           10 0110 0010   nach 1. Aufruf
4962    1362    0001 0011 0110 0010   folgende Aufrufe

                   I  PC            Schutz mit try...
Dec    Hex       C RC  Exception  finally + Setzen "37F"
4991    137F   0001 0011 0111 1111   Delphi vor erster Start
 895     37F          11 0111 1111   manuell gesetzt
 610     262           10 0110 0010   nach 1. Aufruf
 895     37F          11 0111 1111   manuell gesetzt
 866     362           11 0110 0010   folgende Aufrufe

                   I  PC            Schutz mit try...
Dec    Hex       C RC  Exception  finally + Setzen "262"
4991    137F   0001 0011 0111 1111   Delphi vor erster Start
 610     262           10 0110 0010   manuell gesetzt
 610     262           10 0110 0010   nach 1. Aufruf
 610     262           10 0110 0010   manuell gesetzt
 610     262           10 0110 0010   folgende Aufrufe
Fazit:
  • die Dll setzt sich immer die gleiche Exception Mask
  • Der Rundungs-Modus (RC) wird nicht geändert
  • Infinity wird zwar geändert, spielt aber entsprechend der Dokumentation aus dem Link keine Rolle:
    Zitat:

    8.1.6 Infinity Control Flag
    The infinity control flag (bit 12 of the x87 FPU control word) is provided for compatibility with the Intel 287 Math
    Coprocessor; it is not meaningful for later version x87 FPU coprocessors or IA-32 processors. See Section 4.8.3.3,
    “Signed Infinities,” for information on how the x87 FPUs handle infinity values.
    Ist der Wert also jetzt egal?
  • es gibt ggf. eine Abweichung bei Precision Control (PC), mögliche Werte
    Code:
    Precision                          PC Field
    Single Precision (24 bits)         00B
    Reserved                           01B
    Double Precision (53 bits)         10B
    Double Extended Precision (64 bits) 11B
    -> erster Aufruf läuft immer auf "Double Precision", folgende auf "Double Extended Precision", falls der vorherige Wert nicht übereinstimmt. Ich habe mir nicht die genauen Gründe angesehen. Das werde ich mit den Erstellern der dll abklären. Wenn ich dort einfach auch "262" setze, dann wird der Wert nicht geändert.
  • Durch die .dll wird die Eigenschaft des "x87 FPU Control Word" für das Programm verstellt, ich muss/sollte also mir vorher den Wert merken und ich nachher zurücksetzen.

Zitat:

Zitat von Luckie (Beitrag 1393978)
Zitat:

Zitat von hoika (Beitrag 1393975)
Zitat:

Erst wenn ich die Rundungs-Funktion mit "lAnzahlNachkommaStellen + 1" aufrufe, scheint es so, dass ich ein reproduzierbares Ergebnis bekomme...
Das ist genau der richtige Ansatz!

Ich wollte gerade sagen. Das kann es nicht sein, dass man für eine solche Funktion der Maßen tief in den Prozessorinstruktionen eingreifen muss. Wenn das der Fall ist, dann macht man irgendwas verkehrt.

Das wäre mir auch lieber. Aber es hat sich leider bei mir gezeigt, dass auch mit der Rundung andere Ergebnisse (an anderer Stelle) von den "Format" und "ToStr"- Funktionen ausgegeben werden (also wird das Round... schon irgendwie anders arbeiten).
Daher ist das "try ... finally" zum "Merken und Zurücksetzen" schon notwendig, denke ich.


Vielen Dank für eure Hilfe!


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:23 Uhr.
Seite 1 von 2  1 2      

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