Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Real ungenau


Tobias1 - Mi 01.06.05 13:43
Titel: Real ungenau
Wenn ich mit real werten rechne kommt es vor das es anstatt mit 4 mit 3,9999956 oder so rechnet.
Warum ist das so? Muss ich immer den Wert 4,000000 zuweisen?
Gibt es ein Befehl oder einen anderen Variablentyp, der das behebt?

Danke!


Delete - Mi 01.06.05 13:49

Rundungsungenauigkeit, die darin begründet liegt, wie Fließkommazahlen im Speicher abgelegt werden.


Gausi - Mi 01.06.05 13:50

Hallo,

Das hat mehrere Ursachen. Zum einen ist generell die Rechengenauigkeit im Rechner beschränkt. Einige Rechenoperationen sind dabei so bösartig, das sich diese Ungenauigkeiten extrem hochschaukeln. Ein Beispiel für Matrizen ist dabei die Hilbertmatrix. Sie ist zwar invertierbar, aber durch ihre Struktur kommen beim Berechnen der Inversen komplett andere Werte heraus, als man erwarten würde.

Zum anderen lassen sich auch "ganz normale Zahlen" wie z.B. 0.1 im Binärsystem überhaupt nicht exakt darstellen. Auch da arbeitet der PC dann mit Näherungswerten.

So kommt es, dass einige Rechnungen bisweilen Fehler aufweisen. Ein so großer Fehler ist aber schon merkwürdig. Was berechnest du denn in deinem Beispiel?


jasocul - Mi 01.06.05 13:50
Titel: Re: Real ungenau
user profile iconTobias1 hat folgendes geschrieben:
Wenn ich mit real werten rechne kommt es vor das es anstatt mit 4 mit 3,9999956 oder so rechnet.
Warum ist das so? Muss ich immer den Wert 4,000000 zuweisen?
Gibt es ein Befehl oder einen anderen Variablentyp, der das behebt?

1. Weil der PC im Binärsystem rechnet und nicht, wie wir, im Dezimalsystem.
2. Du könntest due Funktion Round benutzen (siehe OH).
3. Meines Wissens nicht. Außer du benutzt Integer. Dann hast du aber keine Nachkommstellen. :wink:


deccer - Mi 01.06.05 13:51

<senf>
auf real sollte man verzichten, da es "veraltet"/"deprecated" ist, stattdessen nimmst lieber
single (einfache genauigkeit), oder double (doppelte genauigkeit) - die genauigkeit bezieht sich auch wieder auf die nachkommastellen
</senf>

@forumscheffs - mir fehlt son "Senf" button im editfenster P:


BenBE - Mi 01.06.05 17:23

Naja, Auf Single und Double sollte er dann getrost verzichten, wenn ihm Real (=Double unter Delphi) schon Probleme macht.

Versuch Dein Glück mal mit Extended (10 Byte), der zwar etwas mehr Code für die CPU bedeutet, aber genauere Ergebnisse liefert und intern schneller verwendet werden kann (da natives Format der FPU).


Tobias1 - Mi 01.06.05 17:29

Danke!


alzaimar - Mi 01.06.05 17:30

Und wer richtig genau werden will, nimmt BCD. Das kann wenigstens 0.1 100% genau darstellen. Leider sind Floatingpoint Operationen sehr viel schneller als BCD, weswegen in den FPU nur floats benutzt werden, was dann den eingangs zitierten Effekt hat.
Immer wieder gern gesehen (1x form, 1x Button, 3xMemo):

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure TForm3.Button1Click(Sender: TObject);
Var e: Extended; s : single; r : Real;
begin
  e:= -1; s:= -1; r:= -1;
  while r<0.2 do begin
    memo1.lines.add (FloatToStr(e));
    memo2.lines.add (FloatToStr(s));
    memo3.lines.add (FloatToStr(r));
    r := r+0.1;
    e := e+0.1;
    s := s+0.1;
    End;
end;


Gausi - Mi 01.06.05 17:41

user profile iconalzaimar hat folgendes geschrieben:
Und wer richtig genau werden will, nimmt BCD. Das kann wenigstens 0.1 100% genau darstellen.

Nur mal aus Neugierde: Was ist/sind BCD, und wie wird damit im Rechner 0.1 genau dargestellt?


BenBE - Mi 01.06.05 18:43

BCD = Binary Coded Decimals...

Da werden einfach die Ziffern der Dezimal-Darstellung als Nibbels eines Bytes gespeichert.

25d --> $25 * 100 ^ 0bcd.


delfiphan - Fr 03.06.05 00:29

user profile iconTobias1 hat folgendes geschrieben:
Wenn ich mit real werten rechne kommt es vor das es anstatt mit 4 mit 3,9999956 oder so rechnet.
Warum ist das so? Muss ich immer den Wert 4,000000 zuweisen?
Gibt es ein Befehl oder einen anderen Variablentyp, der das behebt?

Danke!

Die Zahl 4 sollte dir eigentlich keine Schwierigkeiten bereiten. Sie ist mit Floats exakt darstellbar. Wenn du die Zahl aber mit einer anderen, ungenaueren Zahl verknüpfst, dann kann es schon vorkommen, dass die Zahl dann nicht mehr genau stimmt. Was für eine Rechnung führst du durch?


Tobias1 - Fr 03.06.05 07:53

Division, Multiplikation


alzaimar - Fr 03.06.05 08:17

Dafür gibt es die Schutzstellen: Du rechnest intern (z.B.) mit voller Genauigkeit und nimmst in Kauf, das eben anstatt mit 4.0000 mit 3.9999996 gerechnet wird. Bei Division und Multiplikation wird sich der Fehler i.A. (hier wörtlich zu verstehen) nicht hochschaukeln. Aber Vorsicht bei Iterationen etc.

Dein Ergebnis stellst Du aber nicht mit voller Genauigkeit dar. Im richtigen Leben reichen normalerweise 4 Stellen aus. Alles Andere ist Quark. Beispiel Abmessungen: Es ist interessant, das z.B. die berechnete Länge 4,56786 m ist, aber das ist doch völlig Schnurz, weil im Meter-Bereich normalerweise nur auf mm genau gemessen wird. Wichtig ist, mit Augenmaß und Drang zur Realität die Genauigkeit anzugeben.

Ich würde Extended-Zahlen nehmen und nie auf Gleichheit prüfen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
Var
  a,b : Extended;

Begin
  a := 8.9;
  b := 4.9;
  If a-b = 4 Then Showmessage('Foo');
  If Abs (4 - (a-b)) < 1E10 Then Showmessage('Bar'); 
// Oder 
  If IsZero (4 - (a-b)) Then Showmessage ('Bar 2');
End;

Ob hier 'Foo' angezeigt wird, weiss nich nicht. Aber 'Bar' wird in jedem Fall angezeigt, 'Bar 2' auch.

Im Umgang mit reelen Zahlen wirst Du immer mit Schutzstellen arbeiten, also die letzten paar Stellen ignorieren.


deccer - Fr 03.06.05 09:10

user profile iconBenBE hat folgendes geschrieben:
Naja, Auf Single und Double sollte er dann getrost verzichten, wenn ihm Real (=Double unter Delphi) schon Probleme macht.

Versuch Dein Glück mal mit Extended (10 Byte), der zwar etwas mehr Code für die CPU bedeutet, aber genauere Ergebnisse liefert und intern schneller verwendet werden kann (da natives Format der FPU).


aha, aber dann nur wenn er mit dem FPU asmbefehlssatz arbeitet, ansonsten weiß ja der compiler nicht, obber jetz die fpu oder cpu bemühen soll :!:


delfiphan - Fr 03.06.05 09:27

Darf ich trotzdem nochmal fragen, was du genau berechnen möchtest (evtl. Source?). Vielleicht lässt sich das Problem umschreiben... (Vielleicht auch nicht)


Stefan.Buchholtz - Fr 03.06.05 09:29

user profile icondeccer hat folgendes geschrieben:
user profile iconBenBE hat folgendes geschrieben:
Naja, Auf Single und Double sollte er dann getrost verzichten, wenn ihm Real (=Double unter Delphi) schon Probleme macht.

Versuch Dein Glück mal mit Extended (10 Byte), der zwar etwas mehr Code für die CPU bedeutet, aber genauere Ergebnisse liefert und intern schneller verwendet werden kann (da natives Format der FPU).


aha, aber dann nur wenn er mit dem FPU asmbefehlssatz arbeitet, ansonsten weiß ja der compiler nicht, obber jetz die fpu oder cpu bemühen soll :!:


Da alle x86er-Prozessoren ab dem 486er aufwärts eine FPU integriert haben, benutzt der Delphi-Compiler (und auch alle anderen mir bekannten aktuellen Compiler) für Fließkommaoperationen immer FPU-Befehle.

Stefan


deccer - Fr 03.06.05 09:38

warum rechnest das nich einfach auf ganze zahlen hoch?

4,123123 * 100000 = 4123123 ..... damit lässts sich "einfacher" rechnern


jasocul - Fr 03.06.05 09:46

Ich benutze im Zweifel immer Round.
Auf Ganzzahlen hochrechnen kann schnell zu Problemen führen. Du weißt ja nicht, wieviele Nachkommastellen zu berücksichtigen sind. Wenn du dann noch zuviele Vorkommastellen hast, gibt es sehr lustige Effekte. :mrgreen:


deccer - Fr 03.06.05 10:00

das is klar :)

aber in den meisten fällen hatmer ja nich mehr als 5 oder 6 nachkommastellen (z.b. bei opengl krams) wenn man da nen epsilon oder so brauch .......

naja was solls </senf>


Tobias1 - Fr 03.06.05 13:36

Ich habe jetzt recht viele round-Funktionen,damit gehts, in meinem Code.
Ich wills jetzt aber auf Integer umproggen.


ManuelGS - Sa 04.06.05 18:14

ist das problem eigentlich nur bei delphi vorhanden, oder sind andere programmiersprachen "genauer"? ich find das nämlich auch recht nervig mit ungenauen werten zu arbeiten.


Delete - Sa 04.06.05 18:43

Das hat nichts mit der Programmiersparche zu tun, sondern damit, wie Fließkommazahlen intern abgebildet werden.


delfiphan - Sa 04.06.05 19:33

Gerechnet wird über die FPU. Delphi übersetzt bloss deine Additionen und Multiplikationen in eine für den Chip verständliche Form.

Allerdings kann es vorkommen, dass bei nicht "symmetrischer" Übersetzung der Formeln die Terme nicht ganz gleichberechtigt gerundet werden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
var
 D: Double;
begin
  D := 0.2;
  ShowMessage(FloatToStr(D-0.2)); // "1.10995197408204E-17"
end;

Wenn ich mich nicht täusche passiert das bei Extended nicht; dort werden nämlich immer alle Zahlen zuerst in den Stack geladen, bevor gerechnet wird und somit geschieht die Rundung überall gleich.
Kann dir aber nicht garantieren, dass diese Theorie stimmt; sie basiert auf Beobachtung.