Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   SimpleRoundTo (https://www.delphipraxis.net/172309-simpleroundto.html)

MrSpock 24. Dez 2012 22:13

SimpleRoundTo
 
Ich habe schon die Fragen dazu im Forum gelesen, aber habe immer noch keine gute Antwort gefunden.

Ich benutze SimpleRoundTo, da ich kein kaufmännisches Runden haben möchte.

Habe zwei Extended Werte a = 0.55 und b = 18.5. Das Produkt ist 10.175 müsste also mit SimpleRountTo(a*b, -2) aufgerundet werden auf 10.18.

Im Debugger wird mir der Wert von
Zitat:

(AValue / LFactor) - 0.5) mit 1018
abgezeigt, der Wert von
Zitat:

Trunc(AValue / LFactor) - 0.5) mit 1017
!!??? Grrml

Jetzt habe ich in der Math Bibliothek mal Trunc durch Round ersetzt und mit mrRoundnearest gerundet. Dann zeigt der Degugger:
Zitat:

(AValue / LFactor) - 0.5) mit 1018
uns auch
Zitat:

Round(AValue / LFactor) - 0.5) mit 1018
, aber
Zitat:

(AValue / LFactor) - 0.5)*LFactor mit 10,17
.

Ich will doch einfach nur einen "normal" gerundeten Wert haben. :evil:

Wie erhalte ich hier den aufgerundeten Wert 10.18?

Bummi 24. Dez 2012 22:50

AW: SimpleRoundTo
 
ich habe kein Problem mit:
Delphi-Quellcode:
var
 a,b,c:extended;
begin
a :=0.55;
b := 18.5;
c:=RoundTo(a*b,-2);
Caption := FloatToStr(c);
end;
aber vielleicht habe ich etwas falsch verstanden.

Furtbichler 25. Dez 2012 19:49

AW: SimpleRoundTo
 
Ich habe vor etlichen Jahren einen fiesen Bug in einer Abrechnungssoftware gehabt, aus ähnlichen Gründen.

Ich haben dann die Konstante 0.5 auf 0.5+1E-15 (also ein winzigkleinwenigmehr als 0.5) geändert. (Ich glaube zumindest, das es 1E-15 war, keine Ahnung)

Zitat:

Zitat von MrSpock (Beitrag 1196663)
Ich will doch einfach nur einen "normal" gerundeten Wert haben.

Na ja, so (aber das weißt Du ja):
Delphi-Quellcode:
Trunc(unroundedValue/Granularity+0.5+1E-15)*Granularity

Volker Z. 26. Dez 2012 01:19

AW: SimpleRoundTo
 
Hallo,

ich kann Dein Problem mit dem Rundungfehler leider - noch nicht ganz - nachvollziehen. In Anlehnung an Bummis Beispiel ergibt sich bei mir für:

Delphi-Quellcode:
var
  a, b : Extended;
begin
  a := 0.55;
  b := 18.5;

  Caption := FloatToStr(SimpleRoundTo (a * b))
end;
stets der Wert 10,18 (auch mit anderen Werten für a und b die entsprechend korrekte Rundung); also das, was Du eigentlich auch erwartest.

Gehe ich hingegen her und kopiere mir sozusagen den Code aus SimpleRoundTo und nehme:

Delphi-Quellcode:
var
  f      : Double;
  a, b, c : Extended;
begin
  a := 0.55;
  b := 18.5;

  f := IntPower (10, -2);
  c := Trunc((a * b / f) + 0.5) * f;

  Caption := FloatToStr(c)
end;
erhalte ich das fehlerhafte Ergebnis 10,17. Für a, b, c : Double; erhalte ich korrekte Ergebnisse (SimpleRoundTo erwartet an der Stelle auch Double). Versuch' s doch mal mit Double für a und b.

Zitat:

Zitat von Furtbichler (Beitrag 1196694)
Ich haben dann die Konstante 0.5 auf 0.5+1E-15 (also ein winzigkleinwenigmehr als 0.5) geändert. (Ich glaube zumindest, das es 1E-15 war, keine Ahnung)

Ich will jetzt ja nicht klugsch..., aber nur ein "winzigkleinwenigmehr" in Form von 1E-15 aufzuaddieren scheint mir dann doch nicht die geeignete Lösung zu sein. Besser wäre dann doch mit der relativen Maschinengenauigkeit zu rechnen, als einen x-beliebigen Wert zu nehmen.

Gruß

Popov 26. Dez 2012 06:09

AW: SimpleRoundTo
 
Das sollten klappen. Ob es schön ist, ist eine andere Frage.

Delphi-Quellcode:
function MyRound(Value: Extended): Extended;
begin
  //bei 100 wird auf 2 Stellen gerundet, bei 1000 auf 3, usw.
  Result := Trunc(Value) + (Round(Frac(Value) * 100) / 100);
end;
Macht aus 12,34567 > 12,35

Macht aus 12,34467 > 12,34

Furtbichler 26. Dez 2012 09:47

AW: SimpleRoundTo
 
Popov, bei deiner Lösung wird das Problem auch irgendwann auftreten.
Zitat:

Zitat von Volker Z. (Beitrag 1196708)
Ich will jetzt ja nicht klugsch...,

Dann lass es doch einfach.

MrSpock 26. Dez 2012 13:45

AW: SimpleRoundTo
 
So wie Furtbichler habe ich es in der Vergangenheit auch gemacht.

Ich habe das Testprogramm von Bummi auch getestet und da funktioniert es.

Die Daten kommen aber aus einer Firebird DB und sind TFibFloatFields. Aber ich habe alle Multiplikationen durch Int64 Multipilkationen ersetzt.

Etwa so:

Delphi-Quellcode:
Function Mult3(e1, e2, e3: Extended): Extended;
...
i1 := Round(SimpleRoundTo(e1*1000), 0);
i2 := Round(SimpleRoundTo(e2*1000), 0);
...
Result := i1*i2*i3/1000000000;
end;
Damit berechne ich dann
Delphi-Quellcode:
Erg := Mult3(feld1, feld2, 1.0); // feld1 ist ein DB Feld mit Float Wert 0.55 und Feld2 mit 18.5;

Erg := SimpleRoundTo(erg, -2);
Und dann wird 10,17 ausgegeben. Warum ist das unterschiedlich zu dem Code von Bummi?

MrSpock 26. Dez 2012 13:50

AW: SimpleRoundTo
 
Zitat:

Zitat von Volker Z. (Beitrag 1196708)
Hallo,

ich kann Dein Problem mit dem Rundungfehler leider - noch nicht ganz - nachvollziehen. In Anlehnung an Bummis Beispiel ergibt sich bei mir für:

Delphi-Quellcode:
var
  a, b : Extended;
begin
  a := 0.55;
  b := 18.5;

  Caption := FloatToStr(SimpleRoundTo (a * b))
end;
stets der Wert 10,18 (auch mit anderen Werten für a und b die entsprechend korrekte Rundung); also das, was Du eigentlich auch erwartest.

Gehe ich hingegen her und kopiere mir sozusagen den Code aus SimpleRoundTo und nehme:

Delphi-Quellcode:
var
  f      : Double;
  a, b, c : Extended;
begin
  a := 0.55;
  b := 18.5;

  f := IntPower (10, -2);
  c := Trunc((a * b / f) + 0.5) * f;

  Caption := FloatToStr(c)
end;
erhalte ich das fehlerhafte Ergebnis 10,17. Für a, b, c : Double; erhalte ich korrekte Ergebnisse (SimpleRoundTo erwartet an der Stelle auch Double). Versuch' s doch mal mit Double für a und b.

Zitat:

Zitat von Furtbichler (Beitrag 1196694)
Ich haben dann die Konstante 0.5 auf 0.5+1E-15 (also ein winzigkleinwenigmehr als 0.5) geändert. (Ich glaube zumindest, das es 1E-15 war, keine Ahnung)

Ich will jetzt ja nicht klugsch..., aber nur ein "winzigkleinwenigmehr" in Form von 1E-15 aufzuaddieren scheint mir dann doch nicht die geeignete Lösung zu sein. Besser wäre dann doch mit der relativen Maschinengenauigkeit zu rechnen, als einen x-beliebigen Wert zu nehmen.

Gruß

Also das mit dem Double werde ich versuchen! Hast du eine Begründung, warum Extended hier nicht funktioniert? Das hat doch eine bessere Auflösung. :?

Ich glaube ich versuche einmal die Rundungsfunktionen hier http://cc.embarcadero.com/Download.aspx?id=21909

samso 26. Dez 2012 16:12

AW: SimpleRoundTo
 
Die Floatingpointunit des Prozessors rechnet intern mit dem Typ Extended. Wenn die Zwischenergebnisse im Typ Double gespeichert werden, dann wird auf die geringere Auflösung gerundet (vergleichbar mit einem Taschenrechner der intern mit 12 Stellen rechnet in der Anzeige aber nur 10 Stellen darstellt). Solange ich innerhalb eines Typs bleibe (also Double oder Extended) ist das Endergebnis bei mir korrekt. Erst bei der Mischung der Typen kommt es zu sichtbaren Fehlern. Wenn also a,b vom Typ Extended sind, während f vom Type Double ist, ist der Fehler der Berechnung bei mir so groß, dass das Ergebnis der Rundung falsch ist.
Zitat:

Zitat von MrSpock (Beitrag 1196724)
Und dann wird 10,17 ausgegeben. Warum ist das unterschiedlich zu dem Code von Bummi?

Das wäre nur zu erklären, wenn das Ergebnis der Funktion Mult3 nicht 10,175 sondern kleiner wäre. Da der Fehler schon sehr groß sein muss, damit er im Debugger angezeigt wird, käme es vermeintlich zur falschen Rundung. Mit dem folgende Testprogramm habe ich das getestet:
Delphi-Quellcode:
var
  a: Extended;
begin
   a := 10.175 - 5E-10;
   Caption := FloatToStr(SimpleRoundTo(a));
Der Debugger (Delphi 2007/2010) zeigt a=10,175 an, obwohl der Fehler bereits bei 5E-10 liegt, und damit weit oberhalb der eigentlich erreichbaren Genauigkeit von Extended. Mir ist allerdings nicht klar, wie das Ergebnis von Mult3 in diesen Bereich kommen sollte. Ich habe mich redlich bemüht mit diesen Anfangswerten zu diesem Fehlerbild zu kommen - es klappt nicht.
Edit: Die Berechnung (a * b / f) + 0.5 ergibt einen Wert der etwas kleiner als 1018 ist (der Fehler sollte in etwa bei -2e-14 liegen). Bei der Zuweisung auf Double wird nun durch die Floatingpointunit auf 1018 gerundet. Das nachfolgende Trunc liefert dann den korrekten Integer 1018. Unterbleibt die Zuweisung auf Double und wird mit Extended weiter gerechnet, unterbleibt die prozessorinterne Rundung und trunc liefert den "falschen" Wert.

Bei Delphi 2007 war die Funktion SimpleRoundTo wie folgt deklariert:
function SimpleRoundTo(const AValue: Double; const ADigit: TRoundToRange = -2): Double;

Bei Delphi 2009/2010 (spätere Versionen habe ich hier nicht) war es dann
function SimpleRoundTo(const AValue: Extended; const ADigit: TRoundToRange = -2): Extended;

Deshalb liefert die Funktion bei Delphi 2007 noch den korrekten Wert und bei Delphi 2009/2010 dann nicht mehr.

MrSpock 28. Dez 2012 15:27

AW: SimpleRoundTo
 
Volker Z. hat oben gepostet, wie er es nachbilden konnte.

Obwohl ich auch nicht verstehe warum der Effekt bei der Berechnung auftritt, habe ich (höchstwahrscheinlich) jetzt eine Funktion gefunden, die korrekt funktioniert. Es ist die Funktion DecimalRoundExt für Extended Values. Ich habe sie oben verlinkt und kann sie empfehlen, wenn jemand auch dieses nervige 1 Cent-Problem hat.

Volker Z. 28. Dez 2012 19:57

AW: SimpleRoundTo
 
Liste der Anhänge anzeigen (Anzahl: 2)
Hallo,

Zitat:

Delphi-Quellcode:
var
  a: Extended;
begin
   a := 10.175 - 5E-10;
   Caption := FloatToStr(SimpleRoundTo(a));
Der Debugger (Delphi 2007/2010) zeigt a=10,175 an, obwohl der Fehler bereits bei 5E-10 liegt, und damit weit oberhalb der eigentlich erreichbaren Genauigkeit von Extended.
Da foppt uns wohl der Debugger (siehe Anhänge).

Delphi-Quellcode:
var
  f : Double;
  a, b, c : Extended;
begin
  a := 0.55;
  b := 18.5;

  f := IntPower (10, -2);
  c := a * b; // 10.175 in den Lokalen Variablen, 10.175 in Überwachte Ausdrücke
  c := c / f; // 1017.5 in den Lokalen Variablen, aber 1017.49999999999998 in Überwachte Ausdrücke (und damit wird gerechnet)
  c := Trunc (c + 0.5) * f;
end;
Damit: Trunc(1017.99999999999998) = 1017.

Diese Variante
Delphi-Quellcode:
var
  f : Double;
  a, b, c : Double;
begin
  a := 0.55;
  b := 18.5;

  f := IntPower (10, -2);
  c := a * b; // 10.175 in den Lokalen Variablen, 10.1750000000000007  in Überwachte Ausdrücke
  c := c / f; // 10.175 in den Lokalen Variablen, 10.175 in Überwachte Ausdrücke
  c := Trunc (c + 0.5) * f; // 10.18 in den Lokalen Variablen, aber 10.1799999999999997 in Überwachte Ausdrücke

  Caption := FloatToStr (c) // gibt 18,18 aus hier richtet es FloatToStr
end;
Gruß

samso 29. Dez 2012 11:39

AW: SimpleRoundTo
 
Delphi-Quellcode:
  c := Trunc (c + 0.5) * f; // 10.18 in den Lokalen Variablen, aber 10.1799999999999997 in Überwachte Ausdrücke

   Caption := FloatToStr (c) // gibt 18,18 aus hier richtet es FloatToStr
Das ist ja in diesem Fall auch völlig korrekt, weil der Fehler von 3E-16 innerhalb der maximal mit Double erreichbaren Genauigkeit liegt. FloatToStr ist, soweit ich weiß, für eine Genauigkeit von 16 Ziffern ausgelegt, also etwa der Auflösung von Double.
Der Kern des Fehlers könnte in den verschiedenen Rundungsalgorithmen des Debuggers und von SimpleRoundTo liegen. Der Debugger verwendet ja vermutlich das bankersrounding, während das Programm überwiegend SimpleRoundTo verwendet. Dadurch werden die Werte im Debugger anders dargestellt, als programmintern gerechnet wird und es kommt vielleicht vermeintlich zu einem falschen Ergebnis. Bei den überwachten Ausdrücken wird erfreulicherweise anscheinend der Gleitkommawert komplett dekodiert und nicht ein bereits gerundeter Wert. Es wäre spannend mit dieser Erkenntnis nochmal Mult3 zu debuggen.

MrSpock führt ja aus, dass bei Ihm die Funktion DecimalRoundExt schließlich den Fehler beseitigt hat. Diese Funktion unterscheidet sich von SimpleRoundTo im Wesentlichen dadurch, dass vor dem Runden ein Fehleraufschlag dazu addiert wird. Das bedeutet aber lediglich, dass ein wenig früher nach oben gerundet wird (statt ab 0,005 dann schon ab 0,004999..). Das ist aber nur sinnvoll, wenn man der Auffassung ist, dass es in der vorherigen Berechnung einen konstanten Fehler nach unten gibt (also das Ergebnis von Mult3 stets eher zu klein ist) und keinen zufällig verteilten Fehler. Warum sollte das Ergebnis der Division durch 1E9 immer nach unten vom korrekten Wert abweichen? Bei Gleitkommaberechnungen würde ich aber normalerweise von einem zufällig verteilten Fehler ausgehen (u.a. eben wegen des voreingestellten bankersroundings).

MrSpock 29. Dez 2012 11:45

AW: SimpleRoundTo
 
Ich nutze DecimalRoundExt nicht mit dem Standardwert als "control", sondern mit drHalfUp. Damit habe ich den Effekt, den ich möchte. Zumindest in den Testfällen, die ich benutzt habe. Jetzt muss ich noch die Reaktion des Kunden abwarten, ob im operationellen Einsatz auch kein Cent-Fehler mehr auftritt.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:06 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