Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Problem mit RoundTo((100*0.01),0) (https://www.delphipraxis.net/112284-problem-mit-roundto-100%2A0-01-0-a.html)

zecke 18. Apr 2008 01:15


Problem mit RoundTo((100*0.01),0)
 
:hi:

Nach sehr langer Zeit habe ich mal wieder ein Problem. Mir fällt einfach kein Fehler auf.

Ich möchte Zahlen im Grunde einfach immer abrunden (ohne Nachkommastellen). Das mache ich mit RoundTo und RoundMode habe ich auf truncate.

Erstmal der Code:

Delphi-Quellcode:
   p7:=RoundTo((staedte*0.01),0);
   showmessage(floattostr(staedte));
   showmessage(floattostr(RoundTo((100*0.01),0)));
   showmessage(floattostr(p7));
staedte ist vom Typ Real, ebenso wie p7. Außerdem hat staedte den Wert 100.

showmessage Nummer 1 zeigt mir korrekter Weise "100" an. showmessage Nummer 2 zeigt mir korrekter Weise "1" an. showmessage Nummer 3 zeigt mir "0" an. :wiejetzt: (Die 5000 showmessage Befehle sind übrigens nur zu Demonstrationszwecken vorhanden ;))

Was mache ich denn hier falsch? In der Hilfe habe ich bereits geforscht, auch die Forensuche half mir nicht weiter. Hoffe ich habe keinen Thread übersehen und ihr könnt mir einen Tipp geben. Habe übrigens auch schon die anderen RoundModes ausprobiert, lediglich Up macht es richtig, logisch, aber das will ich ja nicht, falls staedte eine andere Zahl ist.

peschai 18. Apr 2008 05:36

Re: Problem mit RoundTo((100*0.01),0)
 
Hallo
Also ich bin verblüfft! Ich kann das Problem unter Delphi2007Prof nachstellen!
-> Das zweite Showmessage zeigt ein anderes Ergebnis, was nicht sein darf!
Zuerst dachte ich, daß dies ein Problem von REAL sei, aber selbst die Umstellung auf DOUBLE zeigt hier unterschiedliche Werte an!
Delphi-Quellcode:
procedure TForm1.Button4Click(Sender: TObject);
var
  p7 : double;
  staedte : double;
begin
  p7 := 0.0;
  staedte := 100.0;

  SetRoundMode(rmTruncate);

  p7:=RoundTo((100.0*0.01),0); showmessage(floattostr(p7));
  p7:=RoundTo((Staedte*0.01),0); showmessage(floattostr(p7)); // hier anderes ergebnis!
end;

peschai 18. Apr 2008 05:45

Re: Problem mit RoundTo((100*0.01),0)
 
Hallo
Noch ein Ergänzung:
Ein Änderung des Potenzparameters von "0" auf "-1" bringt ebensfalls unterschiedliche Ergebnisse "1" und "0,9"!
Delphi-Quellcode:
  // Staedte, p7 : double; // RoundTo(Double,...)
  SetRoundMode(rmTruncate);
  Staedte := 100.0;
  p7:=RoundTo((100.0*0.01),-1); showmessage(floattostr(p7));
  p7:=RoundTo((Staedte*0.01),-1); showmessage(floattostr(p7));
War so frei und habe bei CodeGear einen BugReport eröffnet, nachdem ich dort in der IssueSuche nichts mit RoundTo gefunden habe.
Zitat:

Report From: Delphi-BCB/Compiler/Delphi Report #: 61093

peschai 18. Apr 2008 06:25

Re: Problem mit RoundTo((100*0.01),0)
 
Hallo,
Dieser Delphi Fehler hat mir keine Ruhe gelassen, aber hier ein ein Workaround:
Habe die original Funktion aus "math" mit extended redefiniert und damit scheint es jetzt zu funktionieren...
Delphi-Quellcode:
function MyRoundTo(const AValue: Extended; const ADigit: TRoundToRange): Extended;
var
  LFactor: Extended;
begin
  LFactor := IntPower(10, ADigit);
  Result := Round(AValue / LFactor) * LFactor;
end;
Das Problem scheint zu sein, daß math.RoundTo mit Double definiert ist, intern aber das System.Round verwendet wird, was aber mit Extended definiert ist. Delphi scheint hier ein Problem zu haben mit der Übergabe der "intern konvertierten werte" an die Round funktion ....

mkinzler 18. Apr 2008 06:35

Re: Problem mit RoundTo((100*0.01),0)
 
Komsich, das dieser Fehler vorher noch niemand aufgefallen zu sein scheint. :gruebel:

peschai 18. Apr 2008 07:08

Re: Problem mit RoundTo((100*0.01),0)
 
@mkienzler
du hast recht, das hat mich auch gewundert. Könnte aber daran liegen daß "SetRoundMode(rmTruncate);" wohl eher selten benutzt wird. Mit der standard Einstellung scheint das problem nicht sichtbar zun werden...

Ich sehe hier ein großes Risiko, daß eventuell alle Funktionen mit Double die intern eine Extended-Funktion aufrufen damit in besonderen fällen (selten?) betroffen sein könnten... Am Liebsten sollten alle diese mit extended redefiniert werden ....

mkinzler 18. Apr 2008 07:20

Re: Problem mit RoundTo((100*0.01),0)
 
Ich verwende eigentlich fast immer Extended, das werden viele andere vielleicht auch machen.

DelphiKlaus 18. Apr 2008 07:32

Re: Problem mit RoundTo((100*0.01),0)
 
Hallo,

liegt das nicht vielleicht einfach daran, dass z.B. 100 * 0.01 nicht unbedingt 1 ergibt, sondern vielleicht 0.99999999.

angos 18. Apr 2008 07:35

Re: Problem mit RoundTo((100*0.01),0)
 
das problem wird eher sein, dass wenn man einer Real-Variable den Wert 100 zuweist, diese einen tatsächlichen Wert von 99,9999999999999999 hat ;) da es ja nicht auftritt, wenn die gesamte berechnung direkt durchgeführt wird.
aber ansich habe ich die gleiche Vermutung wie du... das ist halt einfach der Grund, Fließkommazahlen zu vermeiden wunderbar dargestellt an einem praktischen Beispiel

Gruß

peschai 18. Apr 2008 09:13

Re: Problem mit RoundTo((100*0.01),0)
 
@DelphiKlaus&Angos
Natürlich habt ihr hier recht, das digitale Speichern von Gleitkommazahlen hat dieses Problem grundsätzlich.
Das Verhalten hier ist aber, daß trotz identischen Typen (alles Double) hier ein unterscheidliches Verhalten/Ergebnis auftritt.
Ich hätte weniger ein Problem wenn dieser DigitaleFehler identisch aufgetreten wäre.
Codegear muss hier ein identisches Verhalten sicherstellen!
Delphi-Quellcode:
// Staedte, p7 : double; // RoundTo(Double,...)
SetRoundMode(rmTruncate);
Staedte := 100.0;
p7:=RoundTo((100.0*0.01),-1); showmessage(floattostr(p7));
p7:=RoundTo((Staedte*0.01),-1); showmessage(floattostr(p7));

RavenIV 18. Apr 2008 09:28

Re: Problem mit RoundTo((100*0.01),0)
 
Zitat:

Zitat von peschai
@DelphiKlaus&Angos
Natürlich habt ihr hier recht, das digitale Speichern von Gleitkommazahlen hat dieses Problem grundsätzlich.
Das Verhalten hier ist aber, daß trotz identischen Typen (alles Double) hier ein unterscheidliches Verhalten/Ergebnis auftritt.
Ich hätte weniger ein Problem wenn dieser DigitaleFehler identisch aufgetreten wäre.
Codegear muss hier ein identisches Verhalten sicherstellen!

Warum muss das CodeGear machen?
In anderen Programmiersprachen gibt es dieses Problem sicher auch.

Für die richtige Verwendung von Double ist der Programmierer zuständig.

peschai 18. Apr 2008 10:43

Re: Problem mit RoundTo((100*0.01),0)
 
@RavenIV
ok nocheinmal etwas deutlicher:
Weil es hier um pure Codegear funktionen geht. Es geht hier nicht um den von uns geschriebenen Sourcecode.
Anders formuliert tritt das problem INNERHALB der codegear math.RoundTo Funktion auf:
Delphi-Quellcode:
function RoundTo(const AValue: Double; const ADigit: TRoundToRange): Double;
var
  LFactor: Double;
begin
  LFactor := IntPower(10, ADigit);
  Result := Round(AValue / LFactor) * LFactor;
end;
"Round(AValue / LFactor) * LFactor;" liefert hier unterschiedlich Ergebnisse.
Wenn im besagten Fall für AValue von Aussen "100.0" oder "Staedte:Double=100.0" übergeben wird, so ist das Ergebnis für das Beispiel weiteroben unterschiedlich, was natürlich fatal ist!

Medium 18. Apr 2008 13:35

Re: Problem mit RoundTo((100*0.01),0)
 
Immer das selbe mit den Floats. Es gehört mMn zu den absoluten Grundlagen zu wissen, wie son ein Ding intern ausschaut, und was die Grenzen und Implikationen davon sind. Wenn ich aber etwas verwende, wovon ich gerade mal weiss, dass es das gibt, dann darf ich mich auch nicht wundern, wenn damit Probleme auftreten.
Es ist imho nicht an CodeGear Programmierer über die Bedingungen bei Floats aufzuklären, oder Nichtwissen abzufedern, sondern die Pflicht des Programmieres sich mit den Dingen genau auseinanderzusetzen, die er verwendet. Und dass Floats Ungenaugkeiten nach sich ziehen dürfte hunderttausendfach im Netz dokumentiert sein, allein in der DP sicher schon zigfach. So eine bahnbrechende "Entdeckung" fand hier also ganicht statt ;)

zecke 19. Apr 2008 15:44

Re: Problem mit RoundTo((100*0.01),0)
 
:hi:

Da bin ich doch etwas überrascht, was ich hier losgetreten habe ;)

Nun, es ist egal, was ich verwende (Double,Extended,Real), es kommt trotzdem jedes Mal das Falsche raus. Auch das Workaround von peschai hat nicht das Richtige geliefert (oder ich habe nicht recht verstanden was genau getan wurde, gut möglich). Ich mache es jetzt so, dass ich *1/100 mache, anstatt *0.01. Das liefert das richtige Ergebnis.

Ist ein halbes Jahr her, dass ich Delphi laufen hatte, demnach bin ich also nur Gelegenheits-Noob Programmierer ;) Ich werde mich also nicht für ein kleines Tool zum Privatgebrauch über die einzelnen Typen informieren. Zeitlich eh nicht möglich.

Da es ja nun klappt, soll es für mich damit geklärt sein. Komisch ist es trotzdem. Aber das überlasse ich mal geschmeidig den Profis hier :mrgreen:

Danke alle :thumb:

peschai 21. Apr 2008 05:26

Re: Problem mit RoundTo((100*0.01),0)
 
Hallo Leute,
Das hier beschriebene Problem scheint immer wieder mit den bekannten Unzulänglichkeiten von Gleitkommazahlen (neudeutsch floats) verwechselt zu werden. Hier geht es um etwas anderes. Obwohl zweimal die selbe gleitkommazahl als deselben typ übergeben wurde, ist das ergebnis anders. und das darf nicht sein. Die Ergebnisse dürfen hier falsch (gleitkomma unzulänglichkeiten) sein :-) aber nicht unterschiedlich (issue um was es geht)! Codegear hat dies ebenfalls erkannt.

Bin über die fixe Fehlerbehebung seitens CodeGear positiv überrascht.
"Issue" wurde dort erkannt und innerhalb eines Tages behoben.
Wenn ich mir allerdings die Versionsnummer anschaue, muss ich wohl die nächste Delphi-Version dafür erstehen .... ?
Zitat:

Report #: 61093 Status: Closed
Variable instead of value wrong result in math.RoundTo
Project: Delphi Build #: 11.0.2902.10471
Resolution: Fixed (Resolution Comments) Resolved in Build: : 12.0.0.12454
Comment: Issue no longer present in internal builds. Automated regression test checked-in.

Medium 21. Apr 2008 10:47

Re: Problem mit RoundTo((100*0.01),0)
 
Der Witz ist allerdings irgendwie, dass mein Delphi 7 mit Single, Real (=Double) und Extended bei allen 3 Vatianten genau das richtige Ergebnis liefert. :gruebel:

peschai 22. Apr 2008 05:35

Re: Problem mit RoundTo((100*0.01),0)
 
@Medium
Hast du vorher "SetRoundMode(rmTruncate);" gesetzt ?

xZise 22. Apr 2008 06:37

Re: Problem mit RoundTo((100*0.01),0)
 
Zitat:

Zitat von Medium
Der Witz ist allerdings irgendwie, dass mein Delphi 7 mit Single, Real (=Double) und Extended bei allen 3 Vatianten genau das richtige Ergebnis liefert. :gruebel:

Ich kann das Verhalten nicht bestätigen:
Zuerst kommt 1 dann 0 raus.

MfG
xZise

cruiser 22. Apr 2008 08:08

Re: Problem mit RoundTo((100*0.01),0)
 
Zitat:

Zitat von zecke
... Ich mache es jetzt so, dass ich *1/100 mache, anstatt *0.01. Das liefert das richtige Ergebnis...

ich weiss zwar nicht ob es der Compiler wegoptimiert, aber wäre ein einfaches /100 nichtschon alles was nötig ist? :roteyes: Einen Wert mit 1 zu Multiplizieren macht defacto keinen Sinn ;)

KLS 22. Apr 2008 08:22

Re: Problem mit RoundTo((100*0.01),0)
 
Also mit Version 7.0 (Built 4.453) habe ich auch eine Anzeige von
100 , 1 , 1

Delphi-Quellcode:
uses math;

procedure TForm1.Button1Click(Sender: TObject);
var
  p7,staedte : real;
begin
  staedte := 100;
  p7 := RoundTo((staedte*0.01),0);
  showmessage(floattostr(staedte));
  showmessage(floattostr(RoundTo((100*0.01),0)));
  showmessage(floattostr(p7));
end;
Edit: hab (auch) SetRoundMode(rmTruncate) nicht gesetzt. Dann erhalte ich auch 100,1,0

Medium 22. Apr 2008 09:30

Re: Problem mit RoundTo((100*0.01),0)
 
Zitat:

Zitat von peschai
@Medium
Hast du vorher "SetRoundMode(rmTruncate);" gesetzt ?

Nope, den hab ich natürlich vergessen. Mit diesem erhalte ich auch 100, 1, 0. Das Problem ist das folgende:

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  p7,staedte : real;
begin
  staedte := 100;
  p7 := RoundTo((staedte*0.01),0);
  showmessage(floattostr(staedte));
  showmessage(floattostr(RoundTo((100*0.01),0)));
  showmessage(floattostr(p7));
end;
In dem 2. ShowMessage wird 100*0.01 gerundet, während aber p7 den Wert staedte*0.01 bekommt. Der Knackpunkt ist, dass Delphi konstante Ausdrücke in einem anderen RoundMode berechnet, als unser Programm zur Laufzeit. Ich habe um das zu prüfen die RoundTo Funktion kopiert, und den Parameter AValue in ein array of Byte geschoben, und dann byteweise ausgegeben. Resultat: Die zwei Aufrufe führen zu jeweils unterschiedlichen Darstellungen der 1 binär gesehen. Mit Umwandlung in Strings nicht zu entdecken, da beide Werte durchaus korrekt 1 sind, aber für die FPU beim Runden in diesem Modus einen Unterschied machen!

Das Problem ist letztlich das Setzen des RoundModes selbst, da dies offenbar nicht nur die Rundung betrifft, sondern auch normale Oparationen. Das ist wenn überhaupt also ein Fehler in der FPU, wenn man es aber weiss kann man es umgehen: Einfach den RoundMode als letzten Aufruf vor Round tätigen!

Zur Verdeutlichung:
Delphi-Quellcode:
// So war es bisher, nur die Rechnung auf einzelne Schritte gesplitted
procedure TForm1.Button1Click(Sender: TObject);
var
  p7 : Extended;
  staedte: Extended;
  a1: Extended;
begin
  SetRoundMode(rmTruncate);
  staedte := 100;
  a1 := staedte*0.01;
  p7 := RoundTo(a1,0);
  showmessage(floattostr(staedte));
  showmessage(floattostr(RoundTo((100*0.01),0)));
  showmessage(floattostr(p7));
end;
Delphi-Quellcode:
// Mit dem RoundMode genau vor dem RoundTo gesetzt, geht es auf einmal!
// a1 ist in beiden Fällen zwar 1, allerdings führen die obere und diese
// Versionen zu unterschiedlichen Darstellungen von 1 in a1.
procedure TForm1.Button1Click(Sender: TObject);
var
  p7 : Extended;
  staedte: Extended;
  a1: Extended;
begin
  staedte := 100;
  a1 := staedte*0.01;
  SetRoundMode(rmTruncate);
  p7 := RoundTo(a1,0);
  showmessage(floattostr(staedte));
  showmessage(floattostr(RoundTo((100*0.01),0)));
  showmessage(floattostr(p7));
end;

Tja, und damit lande ich wieder bei meiner ursprünglichen Aussage: Kenne deine Materie...


Edit:
Ein weiterer Effekt dadurch tritt auch auf, wenn man trotz aller Warnungen einen Vergleich per "=" ausführt.
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c: Extended;
begin
  a := 100;
  b := a*0.01;
  c := 1;
  if b=c then
    ShowMessage('b=c');
  if SameValue(b, c) then
    ShowMessage('SameValue(b, c)');
end;
Beide Messages werden angezeigt (was allerdings eher Glück als normal ist), fügt man aber nun an den Anfang der Prozedur ein SetRoundMode(rmTruncate) ein, so zeigt Delphi beim Debuggen immer noch "1" für beide Variablen b und c an, aber das erste ShowMessage tritt nicht ein! Selber Effekt wie oben, mit hübschen Auswirkungen. Man stelle sich ein Programm vor, bei dem nachträglich der RoundMode geändert wird, und viel mit direkten Float-Vergleich gearbeitet wird. (Wobei der Programmiere dann nun schon 2 Kardinalfehler gemacht hätte: RoundMode global für das gesamte Programm setzen, und viel schlimmer die direkten Vergleiche.)

zecke 23. Apr 2008 22:06

Re: Problem mit RoundTo((100*0.01),0)
 
:hi:

Na dann hat sich das ja geklärt. :) Sehr gut.


@cruiser: Ja /100 würde reichen, ich habe *1/100 genommen, damit ich das besser unterscheiden kann. Quasi nur eine optische Hilfe für mich.

Macci 23. Apr 2008 22:18

Re: Problem mit RoundTo((100*0.01),0)
 
Hallo,

Das ganze ist kein Fehler von Delphi.

Gibt man nämlich 100*0.01 ein, berechnet der Compiler das schon im vorherein zu 1, und im Programm steht dann der richtige Wert von 1 drin.

Bei stadte*0.01 kann der Compiler natürlich nichts im voraus berechnen. Da staetde als double (8 Byte) definiert ist, die FPU aber nur mit 10-Byte grossen Zahlen rechnet, muss der Wert von stadte umgerechnet werden. Dabei kommt dann nicht 100 raus, sondern z.B. 99,999999112. Mit 0.01 multipliziert ergibt das dann z.B. 0,99999998123. Bei JEDER Fließkommaoperation treten im Allgemeinen Rundungsfehler auf.

Schließlich wird 0,99999998123 gerundet - und zwar logischerweise zu 0.

Da ist kein Fehler von Delphi. Wenn du das ganze in Assembler programmierst, wird das selbe rauskommen (hier musst du dann mit "FLD QWORD PTR [EAX]" arbeiten, wenn du ausdrücklich double-Werte benutzen willst.)

Viele Grüsse,
Macci

Medium 23. Apr 2008 22:26

Re: Problem mit RoundTo((100*0.01),0)
 
Dennoch ist das beschriebene "Fehlverhalten" hier nicht allein durch die Rundungsfehler an und für sich erklärt, sondern der wichtige Teil ist, dass die FPU andere Rundungsmodi auch beim Rechnen anwendet, und nicht nur beim expliziten Runden. Dadurch ergeben zwei an und für sich identische Rechnungen unterschiedliche Ergebnisse. Beide jedoch im Rahmen der jeweiligen Grenzen von Floats und der eingestellten Logik richtig.

Edit: Mit Extended tritt übrigens das gleiche Verhalten ein. Was hier den unterschied macht ist, dass Delphi zum Zeitpunkt der Kompilierung die "100*0.01" zu 1 optimiert, während die FPU sich im "normalen" Rundungsmodus befindet. Die 100 müssen ja auch irgend einem Register zugewiesen werden, damit der Compiler das ausrechnen kann. Zur Laufzeit jedoch wird vor der Multiplikation der Rundungsmodus geändert, was dazu führt, dass ein anderes Ergebnis heraus kommt als zur Compiletime.

Macci 23. Apr 2008 22:30

Re: Problem mit RoundTo((100*0.01),0)
 
Zitat:

Zitat von Medium
Dennoch ist das beschriebene "Fehlverhalten" hier nicht allein durch die Rundungsfehler an und für sich erklärt,

Doch. Würde die FPU niemals Rundungsfehler machen, würde dieses "Fehlervehalten" hier nicht auftreten.

Zitat:

Zitat von Medium
.. dass die FPU andere Rundungsmodi auch beim Rechnen anwendet, und nicht nur beim expliziten Runden.

Stimmt genau, und das sagte ich ja auch:

Zitat:

Bei JEDER Fließkommaoperation treten im Allgemeinen Rundungsfehler auf.

Medium 23. Apr 2008 23:16

Re: Problem mit RoundTo((100*0.01),0)
 
Worauf ich hinaus wollte war, dass dem TE nie etwas aufgefallen wäre, hätte er den Rundungsmodus nicht geändert. Ich glaube, ich bin einer der letzten hier, denen man die Details von Floats erklären muss ;)

Dazu kommt noch, dass beide Varianten von "1" (Parameter der in die RoundTo-Funktion gegeben wird), die in dem Source des ersten Beitrages vorkommen bei einer Umwandlung zu String durchaus '1' ergeben (was allein sicherlich keine Garantie für 100% Genauigkeit in diesem Fall ist). Sie sind aber binär nicht identisch, weswegen sie innderhalb von RoundTo() zu unterschiedlichen Ergebnissen führen.

Dass Floats ungenau sind, haben wir ja schon zu Genüge in diesem Thread (und im ganzen Forum...) durchgekaut. Interessant war jetzt nur noch der Aspekt, warum es in dem einen Fall zum erwarteten Ergebnis führt, und im anderen nicht, obwohl die Ausgangssituation naiv gesehen gleich ist (was sie jedoch nicht ist).


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