Autor Beitrag
Nersgatt
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1581
Erhaltene Danke: 279


Delphi 10 Seattle Prof.
BeitragVerfasst: Fr 13.01.17 14:33 
Moin,

kann mir jemand dieses Phänomen erklären:
ausblenden 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:
ausblenden 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?

_________________
Gruß, Jens
Zuerst ignorieren sie dich, dann lachen sie über dich, dann bekämpfen sie dich und dann gewinnst du. (Mahatma Gandhi)
Mathematiker
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 2622
Erhaltene Danke: 1447

Win 7, 8.1, 10
Delphi 5, 7, 10.1
BeitragVerfasst: Fr 13.01.17 14:56 
Hallo,
"schönes" Ergebnis.
Ich habe mal noch etwas getestet.
ausblenden 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öten im Krieg ist nach meiner Auffassung um nichts besser als gewöhnlicher Mord. Albert Einstein
t.roller
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 118
Erhaltene Danke: 34



BeitragVerfasst: Fr 13.01.17 15:03 
ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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.

Für diesen Beitrag haben gedankt: Nersgatt
Nersgatt Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1581
Erhaltene Danke: 279


Delphi 10 Seattle Prof.
BeitragVerfasst: Fr 13.01.17 16:28 
user profile icont.roller hat folgendes geschrieben Zum zitierten Posting springen:
ausblenden 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

_________________
Gruß, Jens
Zuerst ignorieren sie dich, dann lachen sie über dich, dann bekämpfen sie dich und dann gewinnst du. (Mahatma Gandhi)
GuaAck
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 376
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: 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

Für diesen Beitrag haben gedankt: Nersgatt
ub60
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 762
Erhaltene Danke: 127



BeitragVerfasst: 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

Für diesen Beitrag haben gedankt: Nersgatt