Delphi-PRAXiS
Seite 3 von 3     123   

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)

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 16:09 Uhr.
Seite 3 von 3     123   

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