Autor Beitrag
user32
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 55
Erhaltene Danke: 5



BeitragVerfasst: Do 14.12.17 21:01 
Zeit für eine kleine Geschichte :-)

Es war einmal ein Logarithmus von 16384. Seine Basis war 4.

ausblenden Delphi-Quelltext
1:
pow := trunc(ln(16384)/ln(4));					


Na, wer denkt, dass pow 7 sein wird? (16384 sind 4^7)



Falsch!

user defined image


Naja gut, wir wissen ja alle, die CPU speichert die Zahl nicht als 7, sondern als 6.999999998 oder so, ab. Und ein Trunc bewirkt dann, dass auf 6 gerundet wird.
Richtig?

Falsch!

7 lässt sich als Gleitkommazahl EXAKT darstellen.

Mantisse 1.11 Exponent 10
= 1.75 * 2^2 = 7.000000000

Warum ist Trunc von 7.00 dann 6?


user defined image

Jetzt ist das Ergebnis also plötzlich 7. Alles ist genau wie vorher, nur dass ich das Ergebnis des Logarithmus vorher speichere.


Naja, vielleicht ist das Ergebnis der Division oder des Logs gar nicht 7, sondern eben die besagten 6.99999997. Besonders die Logfunktionen sind sehr ungenau.


Falsch.

user defined image

Das Ergebnis in der FPU ist in beiden Fällen Exakt 7!

Das bedeutet, Trunc schafft es irgendwie, aus einer "echten" 7, das heißt, 7.0000000000000, eine 6 zu machen. Aber nur manchmal.



Es geht weiter. Jetzt kommt der absolute Oberknaller.

Selbe Situation wie oben, mit Variable. Allerdings habe ich diesmal den Roundingmode explizit auf down gesetzt...


user defined image
user defined image
user defined image


Seht ihr, was ich sehe?

1. FPU berechnet Log. Ergebnis 7.0000 wird in Double-Variable gespeichert.
2. FPU lädt Variable. Immer noch 7.0000.
3. Trunc macht eine 6 draus.


Warum? Welche Magie ist hier am Werk?
Ich bin mit meinen Erklärungsansätzen am Ende. Wer will es versuchen?

Meine Theorie ist also, dass der Rounding Mode Einfluss auf Funktionen wie div oder log hat, aber man davon nichts sieht...

Theorie : Der Delphi Debugger zeigt ungenaue Zahlen. Wenn da steht "Valid 7" könnte es vielleicht eine 6.99999999999999998 sein.

Gegenargument #1: Dann müsste man zumindest in der Variable die exakte Zahl finden können (Im Zweierkomplement).

Lektion: Man sollte sich sehr, sehr, sehr genau überlegen, ob und wann, man Trunc, Round, oder Ceil, verwendet.
Ich hatte deswegen eine Menge Ärger. Überhaupt muss man mit Fließkommazahlen höllisch aufpassen.

Vielen Dank fürs Lesen ;)

Für diesen Beitrag haben gedankt: Symbroson
user32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 55
Erhaltene Danke: 5



BeitragVerfasst: Do 14.12.17 21:09 
Ich hab es selbst gelöst. Mein "Gegenargument" lag richtig.

user defined image

Hier sieht man, welcher Wert in der Variable "tmp" wirklich gespeichert ist.
In ECX das Low Word und in EAX das High.

Das ist aber nicht 7...


user defined image

Fazit: Delphi bescheisst! Das schockiert mich ein bisschen.

Nochmal: VORSICHT MIT FLOATS!

Das Thema ist erledigt, vielleicht findet es trotzdem noch jemand interessant. Kommentare sind willkommen ;)


user32

Für diesen Beitrag haben gedankt: Symbroson
Gammatester
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 323
Erhaltene Danke: 99



BeitragVerfasst: Do 14.12.17 23:36 
Du hast zwar nicht geschrieben, mit welcher Delphi-Version Du rechnest, aber aus der CPU-Darstellung sieht man, daß es nicht 64-Bit ist, und dort hast Du dieses Phänomen nicht. Das folgende Programm
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
var
  pow: integer;
  tmp: double;
begin
  writeln('-----------------');
  pow := trunc(system.ln(16384)/system.ln(4));
  writeln('pow direkt = ', pow);
  tmp := system.ln(16384)/system.ln(4);
  writeln(tmp:25:17,  '    ', Dbl2Hex(tmp));
  pow := trunc(tmp);
  writeln('pow mit dbl = ', pow);
end.
ergibt für 32-Bit
ausblenden Quelltext
1:
2:
3:
pow direkt = 6
      7.00000000000000000    401C000000000000
pow mit dbl = 7
und für 64-Bit
ausblenden Quelltext
1:
2:
3:
4:
-----------------
pow direkt = 7
      7.00000000000000000    401C000000000000
pow mit dbl = 7

Wie kommt das? Die Erklärung ist, daß bei 32-Bit trunc(system.ln(16384)/system.ln(4)) mit Extended-Präzision und nicht mit Double-Präzision gerechnet wird, es werden also verschiedene Zahlen getrunct (genauer: es wird der Extended-Wert direkt getrunct. Beim Umweg über tmp wird der Wert richtig zu tmp=7 gerundet). Hier ein erweitertes 32-Bit Programm, das die Interna zeigt
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
var
  pow: integer;
  tmp: double;
  ext: extended;
begin
  writeln('-----------------');
  ext := system.ln(16384)/system.ln(4);
  writeln(ext:25:19'    ', Ext2Hex(ext));
  pow := trunc(ext);
  writeln('pow mit ext = ', pow);
  tmp := system.ln(16384)/system.ln(4);
  writeln(tmp:25:17,  '    ', Dbl2Hex(tmp));
  pow := trunc(tmp);
  writeln('pow mit dbl = ', pow);
end.
mit dem Ergebnis
ausblenden Quelltext
1:
2:
3:
4:
5:
-----------------
    7.0000000000000000000    4001DFFFFFFFFFFFFFFF
pow mit ext = 6
      7.00000000000000000    401C000000000000
pow mit dbl = 7

Das heißt, der Extended-Wert weicht 1 ulp vom wahren Wert ab, mehr kann man bei transzendenten Funktionen nicht erwarten.

Dein eigentliches Problem ist, daß trunc, round etc keine stetigen Funktion sind, und bei integer bzw. integer + 1/2 Sprünge machen.
user profile iconuser32 hat folgendes geschrieben Zum zitierten Posting springen:
Fazit: Delphi bescheisst! Das schockiert mich ein bisschen.

Nochmal: VORSICHT MIT FLOATS!
Nein, Delphi bescheisst nicht, Du hast zwei verschiedene Rechnungen. 'Vorsicht mit Floats' kann man mit Einschränkungen zustimmen, meist liegt es aber am Unwissen der Benutzer.

(PS: dbl2hex und ext2hex sind meine Funktionen zu Anzeige der internen Darstellung, ein vereinfachte Byte-Darstellung kann mit mir Debuggeranzeige Evaluate/Modify erhalten, zB 'ext,mh').

Für diesen Beitrag haben gedankt: user32
user32 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 55
Erhaltene Danke: 5



BeitragVerfasst: Fr 15.12.17 03:23 
user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
Wie kommt das? Die Erklärung ist, daß bei 32-Bit trunc(system.ln(16384)/system.ln(4)) mit Extended-Präzision und nicht mit Double-Präzision gerechnet wird, es werden also verschiedene Zahlen getrunct (genauer: es wird der Extended-Wert direkt getrunct. Beim Umweg über tmp wird der Wert richtig zu tmp=7 gerundet).

Hast recht, ich hab es zwar auch mit Extended probiert, allerdings nicht "so".

Ja ich nutze Delphi 7 32bit.


user profile iconGammatester hat folgendes geschrieben:
Dein eigentliches Problem ist, daß trunc, round etc keine stetigen Funktion sind, und bei integer bzw. integer + 1/2 Sprünge machen.

Das ist eigentlich gar nicht das Problem, ich verwende halt nur trunc meistens aus Gewohnheit, nach dem Motto "lieber zu wenig als zu viel". Bei der Anwendung war es allerdings fatal und round wäre "richtiger" gewesen (es geht um eine FFT).


Das was der Delphi 7 Debugger hier macht, ist aber definitiv Beschiss:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  asm
    jmp @@start
      @@zahl: dd  $FFFFFFFF$401BFFFF //double 6.9999999999999990
    @@start:
      fld     qword ptr [@@zahl]       // ST0 = "7"
      fstp    st(0)
  end;

user defined image

Hier lade ich sogar explizit 6.9999999999999990 und er zeigt wieder 7 an. Dass ist einfach falsch, da müssen wir glaube ich nicht drüber streiten. Auch wenn es das niederwertigste Bit ist. 6,Periode 9 sind einfach nicht 7. Aber vielleicht sehe ich es zu mathematisch.

Hab mich gerade wieder erinnert, dass man sich auch die Words anzeigen lassen kann. Werde da wohl in Zukunft öfter drauf zurück greifen müssen bei so Sachen.

user defined image