AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

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

Ein Thema von zecke · begonnen am 18. Apr 2008 · letzter Beitrag vom 24. Apr 2008
Antwort Antwort
Seite 3 von 3     123   
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#21

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

  Alt 22. Apr 2008, 10:30
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.)
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Benutzerbild von zecke
zecke

Registriert seit: 17. Jan 2004
494 Beiträge
 
Turbo Delphi für Win32
 
#22

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

  Alt 23. Apr 2008, 23:06


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.
mfg zecke
  Mit Zitat antworten Zitat
Macci

Registriert seit: 31. Mai 2007
129 Beiträge
 
#23

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

  Alt 23. Apr 2008, 23:18
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
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#24

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

  Alt 23. Apr 2008, 23:26
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.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Macci

Registriert seit: 31. Mai 2007
129 Beiträge
 
#25

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

  Alt 23. Apr 2008, 23:30
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 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.
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#26

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

  Alt 24. Apr 2008, 00:16
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).
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 3 von 3     123   


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:24 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