Delphi-PRAXiS
Seite 1 von 2  1 2      

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.


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