Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Trunc liefert unerwartetes Ergebnis


Nersgatt - Fr 13.01.17 14:33
Titel: Trunc liefert unerwartetes Ergebnis
Moin,

kann mir jemand dieses Phänomen erklären:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var
  x : Double;
  y : integer;
begin

  x := 8.6;
  y := Trunc(x * 100);
  self.Caption := IntToStr(y); // Ergibt 859! Hä??

end;


Warum ergibt das 859?

Macht man einen Zwischenschritt mit einer Doublevariable, dann kommt das erwartete Ergebnis raus:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
var
  x, z : Double;
  y : integer;
begin

  x := 8.6;
  z := x * 100;
  y := Trunc(z);
  self.Caption := IntToStr(y); // Ergibt 860

end;


Was passiert da im Hintergrund, warum ist das so?


Mathematiker - Fr 13.01.17 14:56

Hallo,
"schönes" Ergebnis.
Ich habe mal noch etwas getestet.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
var
  x : double;
  y : integer;
begin
  x := 8.6;
  y := Trunc(x * 100);
  listbox1.Items.Add(IntToStr(y)); // Ergibt 859! Hä??
  y := Trunc(x * 1000);
  listbox1.Items.Add(IntToStr(y)); // Ergibt 8599! Hä??
  y := Trunc(x * 10000);
  listbox1.Items.Add(IntToStr(y)); // Ergibt 85999! Hä??
  y := Trunc(x * 100000);
  listbox1.Items.Add(IntToStr(y)); // Ergibt 859999
  listbox1.Items.Add(floatToStr(x-8.6)); // Ergibt -3.55618312 E-16
end;

Verrückt ist, dass beim Typ single das korrekte Ergebnis kommt; bei Extended auch.
Irgendwie muss 8.6 beim Typ double als 8.5999999999999994 abgespeichert werden, aber 8.6*100 eben "genauer", denn dort ergibt floatToStr(z-860) glatt 0.
Dann wird aber beim Typ Extended keine 0 sondern 5,55...E-17. Verrückt.

Steffen


t.roller - Fr 13.01.17 15:03


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure TForm1.Button4Click(Sender: TObject);
var
  x, z : Double;
//  y : integer;
begin
  x := 8.6;
  z := x * 100;
//  y := Trunc(z); // Wozu ???
  self.Caption := FloatToStr(z); // Ergibt 860
end;


jaenicke - Fr 13.01.17 16:05

Der Grund ist sehr einfach, wenn man mal einen Blick in den generierten Assemblercode wirft. Der funktionierende Code mit Zwischenvariable benutzt nach der Multiplikation fstp und fld um den Wert in die Variable zu speichern und wieder daraus zu laden. Dabei wird der Wert in Single oder Double Genauigkeit konvertiert.

Ohne Zwischenvariable wird der Wert direkt an Trunc verfüttert. Der Rohwert ist noch nicht auf eine bestimmte Genauigkeit gerundet, so dass das bekannte Ergebnis dabei herauskommt.

Deshalb empfehle ich auch bei Rechnungen nicht zu viel in eine Zeile zu schreiben. ;-)
Etwas langsamer sind die Zwischenvariablen zwar, aber dafür funktioniert es in der Regel eher so wie man es meinte.


Nersgatt - Fr 13.01.17 16:28

user profile icont.roller hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
//  y := Trunc(z); // Wozu ???                    


Das war ein gekürzten Beispiel, um das Problem darzustellen. In der realen Anwendung wird der Wert als integer in einer Datenbank gespeichert.

Und dann ruft der Kunde an und die sagt 'wenn ich 8.6 eingebe, und das Formular wieder öffnen, steht da 8.59'
Über solche Anrufe freut man sich Freitags kurz vorm Wochenende... :D


GuaAck - Sa 14.01.17 02:20

Hallo,

der Hintergrund liegt in der binären Darstellung (IEEE o.ä.). Dezimal werden Zahlen als Potenzen von 10 dargestellt, im Rechner binär als Potenzen von 2.

8,6 = 8+ 6*10^-1 ist klar.

Binar: Die Reihe 8 + 1/2 + 1/16+1/32+1/256 ... konvergiert zwar gegen 8.6, kann es aber nicht exakt treffen.

Anders ausgedrückt: Von allen reellen Zahlen kann man im dezimalem Format x.y nur die treffen, die ab 0 auf dem 0.1-Raster liegen. Im Binärsytsem nur die, die im 1/2 Raster liegen. In beiden kann man das Raster beliebig verfeinern (Nachkommatsellen erhöhen), die Raster kommen aber nie zur Deckung, es passt nur an einigen Stellen (z. B. 0.5 und 0.75)

So wird aus der dezimalen 8.6 eine binäre Zahl, die um einen Rundungsfehler von der 8.6 abweicht. Daher auch die alte Progrmmiererweisheit: Real-Zahlen nie per "=" auf Gleichheit abfragen.

Gruß Guaack


ub60 - Sa 14.01.17 02:58

Ich möchte die IEE 754-Darstellung noch etwas konkretisieren.

Die Zahl 860 wird dargestellt als:
"0 10000001000 1010111000000000000000000000000000000000000000000000" (binär, Vorzeichen-Exponent-Mantisse),
das sind genau 860.0, hier klappt alles.

Die Zahl 9.6 hingegen wird intern zu:
"0 10000000010 0001001100110011001100110011001100110011001100110011" (binär, Vorzeichen-Exponent-Mantisse), das sind
8.5999999999999996447286321199499070644378662109375 dezimal.

Hier erkennt man die Fehlerursache.

Die nächst größere Binärzahl wäre ja
"0 10000000010 0001001100110011001100110011001100110011001100110100" (binär, Vorzeichen-Exponent-Mantisse),
und diese Zahl ist genau
8.60000000000000142108547152020037174224853515625

Zwischen diesen beiden Zahlen gibt es also im Double-Speicherbereich keine Zahl mehr.

ub60