Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Additionsproblem


Dusty - Sa 02.03.13 14:28
Titel: Additionsproblem
Es scheint Zahlen zu geben, mit denen Delphi ein Problem mit dem rechnen hat.
Das ist mir unerklärlich, aber evt. kann mir hier jemand auf die Sprünge helfen.

Wenn ich 100,29 + 19,06 rechne, kommt 119,35 raus.
Wenn Delphi das rechnet, kommt offenbar etwas anderes dabei heraus?

Ein Formular mit Button und Editfeld reicht um das Problem klar zu machen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.Button1Click(Sender: TObject);
  var d1, d2, d3, d4 : double;
begin
  d1 := StrToFloat('100,29');
  d2 := StrToFloat('019,06');
  d3 := StrToFloat('119,35');
  edit1.text := FloatToStr(d1+d2-d3);
end;

In meinem Editfeld steht jetzt: 1,06581410364015E-14 obwohl ich Null erwarten würde.


2. Kusiosum:

Ein Formular mit einem Button und einem Memofeld.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TForm1.Button2Click(Sender: TObject);
  var d : double;
      i : integer;
begin
   Memo1.Lines.Clear;
   d := 0;
   For i := 1 to 100 do begin
      Memo1.Lines.Add(FloatToStr(d));
      d := d + 0.001;
   end;
end;

Irgendwann tauchen im Memo1 diese Zahlensprünge auf:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
0,082
0,083
0,084
0,085
0,086
0,087
0,0880000000000001
0,0890000000000001
0,0900000000000001
0,0910000000000001
0,0920000000000001

Wo kommt das her? Wer kann mir auf die Sprünge helfen??

Delphi5 Enterprise / Windows XP

Moderiert von user profile iconNarses: Delphi-Tags hinzugefügt
Moderiert von user profile iconNarses: Topic aus Sonstiges (Delphi) verschoben am Mo 04.03.2013 um 09:26


WasWeißDennIch - Sa 02.03.13 14:40

http://www.delphipraxis.net/173534-double-immer-maxdouble.html [http://www.delphipraxis.net/173534-double-immer-maxdouble.html]


Mathematiker - Sa 02.03.13 14:44

Hallo,
double, real, extended ... sind alles Datentypen, bei denen nicht die Ziffern sondern eine Näherungsdarstellung(!) gepeichert werden.
double ist auf 14 Stellen theoretisch genau. Durch die Summierung kommt es aber eher zu einer Abweichung. Nimmst Du extended statt double geschieht dies entsprechend später.
Bei Deinem 1.Beispiel entspricht die Abweichung gerade der Genauigkeit von double.
Übrigens ist dies kein spezielles Problem von Delphi. Diese Abweichungen treten bei allen, derartigen Programmiersprachen auf.

Beste Grüße
Mathematiker


mandras - Sa 02.03.13 14:44

Das Problem ist schön erläutert unter:

http://de.wikipedia.org/wiki/Gleitkommazahl

insbes. unter Überschrift "Dezimalzahlen", wo darauf hingewiesen wird,
daß schon Zahlen wie 0.1 nur näherungsweise darstellbar sind.


Dusty - Sa 02.03.13 14:54

Okay, also ganz normal ... keine fehlerhaften Einstellungen oder sowas.
Danke für eure Hilfe!


Dusty - Sa 02.03.13 15:18

Was mich am meisten irritiert hatte, war, dass der Debugger immer GLEICHE Zahlen angezeigt hat.
Der Debugger ist Schuld ;-)


Tranx - Sa 02.03.13 15:56

Diese "Ungenauigkeit" ist auch der Grund dafür, dass mit solchen Zahlen oder Ergebnissen von Rechnungen keine Vergleiche mit 0 Sinn machen:



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
    a := 0.01;
    b := 0.0212;
    c := 0.0312;
    d := c - b;
    if a-d=0.0 then //wird u.U. nicht funktionieren
      tuewas;

    if abs(a-d)<1e-13 then //das funktioniert schon eher
      tuewas;


Dusty - Sa 02.03.13 18:42

Ja, so ähnlich habe ich es dann auch gemacht.
Allerdings reicht mir ein > 0.001 :-)

Wie gesagt, im Debugger wurden die GLEICHEN Zahlen angezeigt und das machte die Fehlersuche beschwerlich ;-)
Die Methode "Abs(x-y) > 0.001" ist natürlich die richtige Lösung!

Hm - größer oder kleiner... ?! Kleiner für den Vergleich auf Gleichheit ... ok
Größer um den potientiellen Rundungsfehler von 1 Cent aufzuspüren ;-)


WasWeißDennIch - Sa 02.03.13 18:57

Dafür gibt es SameValue [http://docwiki.embarcadero.com/Libraries/XE3/de/System.Math.SameValue] in Unit math.


Dusty - Sa 02.03.13 19:08

Aber noch nicht in Delphi 5 :-)


Tranx - Sa 02.03.13 21:44

Das ist kein Problem, kann man doch nachbilden:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
function SameValue(const A, B: Extended; Epsilon: Extended): Boolean; overload;
function SameValue(const A, B: Double; Epsilon: Double): Boolean; overload;
function SameValue(const A, B: Single; Epsilon: Single): Boolean; overload;

function NotDef(const a : Extended) : boolean; overload;
function NotDef(const a : Double) : boolean; overload;
function NotDef(const a : Single) : boolean; overload;

function NotDef(a: Extended): Boolean;
var
  b: extended;
begin
  try
    if a = 0.0 then
      Result := False
    else
    begin
      b := a * 1.0001;
      if (b = a) then
        Result := True
      else
        Result := False;
    end;
  except
    Result := True;
  end;
end;

function NotDef(a: Double): Boolean;
var
  b: extended;
begin
  try
    if a = 0.0 then
      Result := False
    else
    begin
      b := a * 1.0001;
      if (b = a) then
        Result := True
      else
        Result := False;
    end;
  except
    Result := True;
  end;
end;

function NotDef(a: Single): Boolean;
var
  b: extended;
begin
  try
    if a = 0.0 then
      Result := False
    else
    begin
      b := a * 1.0001;
      if (b = a) then
        Result := True
      else
        Result := False;
    end;
  except
    Result := True;
  end;
end;

function SameValue(const A, B: Extended; Epsilon: Extended): Boolean;
begin
  if NotDef(a) or NotDef(b) then
    Result := False
  else
    Result := (Abs(A - B) < Epsilon);
end;


function SameValue(const A, B: Extended; Epsilon: Extended): Boolean;
begin
  if NotDef(a) or NotDef(b) then
    Result := False
  else
    Result := (Abs(A - B) < Epsilon);
end;

function SameValue(const A, B: Double; Epsilon: Double): Boolean;
begin
  if NotDef(a) or NotDef(b) then
    Result := False
  else
    Result := (Abs(A - B) < Epsilon);
end;

function SameValue(const A, B: Single; Epsilon: Single): Boolean;
begin
  if NotDef(a) or NotDef(b) then
    Result := False
  else
    Result := (Abs(A - B) < Epsilon);
end;


Anmerkung: Fehler bei Übergabe von 0 an die Funktion NotDef abgefangen! Manchmal ist man sowas von der Rolle. O*irgendwas ergibt ja immer 0 und damit würde 0 eine nicht definierte Zahl ergeben, aber dem ist ja nicht so. Also wird vorher auf 0 abgefragt.

Fie Funktion NotDef übergibt bei Zahl = +/-INF TRUE ansonsten FALSE. (Test auf definirte bzw. nicht definierte Zahl).
ich habe mir gesagt, und das hat was für sich, dass eine nicht definierte Zahl (+/-INF) ja nicht unbedingt gleich einer anderen nicht definierten Zahl ist, daher das Ergebnis FALSE, falls eine oder beide Zahlen nicht definiert sind. Interessanterweise macht der Rechner eine Multiplikation mit 1.0001 bei einer nicht definierten Zahl Es ergibt sich aber in dem Fall die gleiche nicht definierte Zahl. So kann man definierte Zahlen von nicht definierten unterscheiden.

Die Funktion Samevalue arbeitet auch mit nicht definierten Zahlen, ist getestet.


Dusty - So 03.03.13 20:22

Klasse :-)