Widersprüchliche Rechenergebnisse
-
Hallo, danke für die Antworten
@daddyfelix
Also 1.19209e-07 ist in der "recheninternen Repräsentation" einmal die Mantisse 1.19209 sowie der Exponent e-07, sowie die jeweiligen Vorzeichen bits der beiden.
für die binäre Darstellung aller Werte müsst ich mich erst mal ne Weile hinhocken.
Was ich aber so oder so nicht verstehe, ist dass wenn ich die Rechnung a+b+c in die zwei Rechnungen s1 = a+b und s1+=c splitte unterschiedliche Werte herauskommen.@xStrykex
Stimmt, die 1.234567e-7 kann ich nachvollziehen. Aber wie kommt dann das Programm auf die 1.19209e-07?
-
Die großen Fehler kommen hier übrigens aus zwei Rechnungen:
1. a + b: Etwas sehr kleines + etwas sehr großes, da geht die Genauigkeit des kleinen verloren, da der Großteil der Darstellung bereits für die große Zahl draufgeht. (Stichwort: absorption)
2. s1 - c: Zwei Zahlen fast gleicher Größe werden subtrahiert, dadurch bekommt man einen gigantischen relativen Fehler. (Stichwort: cancellation)Da das die beiden grundlegenden Fehler der Numerik sind (und auch die beiden ersten, die auf Wikipedia aufgelistet werden), gehe ich mal davon aus, dass dieser Rechenweg mit Absicht so gewählt worden ist, um als abschreckendes Beispiel zu dienen.
edit: Der Threadersteller hat noch etwas gefragt, während ich schrieb:
Stimmt, die 1.234567e-7 kann ich nachvollziehen. Aber wie kommt dann das Programm auf die 1.19209e-07?
Ist hoffentlich nun klar, siehe oben
Was ich aber so oder so nicht verstehe, ist dass wenn ich die Rechnung a+b+c in die zwei Rechnungen s1 = a+b und s1+=c splitte unterschiedliche Werte herauskommen.
Spekulation ohne jetzt genau darüber nachzudenken: Diese Rechnung darf komplett im Rechenwerk durchgeführt werden, ohne zwischendurch in eine Variable begrenzter Genauigkeit zurück geschrieben zu werden. Das Rechenwerk ist wesentlich genauer (in der Regel 80 Bit), da reicht die hier nötige Genauigkeit für die Zwischenschritte locker aus. Wie genau Rechnungen mit Fließkommazahlen durchgeführt werden ist im C++-Standard (bewusst) nur sehr vage beschrieben. Auf den allermeisten Systemen wird jedoch nach IEEE 754 gerechnet, außerdem kannst du auf einem x86(-64) davon ausgehen, dass entweder der x87-Coprozessor oder die SSE-Einheit zum Rechnen benutzt wird (je nach Optimierungseinstellungen).
-
Ich würde einen Erfahrenen mal bitten, sich das hier kurz anzugucken. Dadurch bin ich damals auf die Lösung des Problems, bzw eher gesagt, warum das so ist, gekommen.
http://www.tutorials.de/c-c/381980-problem-gleitkommatypen-assoziativgesetz.html#post1975101
-
Ist jetzt nur eine Vermutung, aber evtl. wird die Addition im letzten Fall einfach wegoptimiert, da sinnlos. Also hast du da evtl. einfach nur
std::cout << a << std::endl;stehen.
Im vorherigen Fall müssen die Additionen aber tatsächlich durchgeführt werden, weil du ein Zwischenergebnis ausgibst, da kann nicht optimiert werden.
Ist nur eine Vermutung, klingt für mich aber plausibel.

-
mantiz schrieb:
Ist jetzt nur eine Vermutung, aber evtl. wird die Addition im letzten Fall einfach wegoptimiert, da sinnlos.
Nein, Fließkommaoperationen können in der Regel nicht wegoptimiert werden, da die Regeln sehr kompliziert sind. Siehe eben dieses Beispiel, wieso das nicht einfach alles weggekürzt werden kann. Es ist schon immer wieder für eine Überraschung gut, wenn gewisse Operationen zur Compilezeit durchgeführt werden (das könnte hier übrigens tatsächlich passieren, wenn man die Optimierungen anschaltet), weil die dann auch gerne mal mit anderer Präzision durchgeführt werden als man das erwarten würde (z.B. mit 64-Bit-Genauigkeit, anstatt mit 80-Bit). Ich habe oben schon genau erklärt, was vermutlich passiert.
-
climits schrieb:
Also 1.19209e-07 ist in der "recheninternen Repräsentation" einmal die Mantisse 1.19209 sowie der Exponent e-07, sowie die jeweiligen Vorzeichen bits der beiden.
Nö - Dein PC denkt binär, nicht dezimal
climits schrieb:
für die binäre Darstellung aller Werte müsst ich mich erst mal ne Weile hinhocken.
rechne mal 2^-23 aus

-
Wobei ich aber nicht sehe, wo hier die cancellation zum Tragen kommt. s1 = a+b = 1.0000001234567 gibt ja gerundet auf 6 Nachkommstellen wieder 1.000000. s1 + c = 1.000000 - 1.000000 müsste ja dann 0 ergeben ???
Edit
@ Walter
Oh eh, verblüffend
-
Rechne deinen Code z.B. mal mit setprecision(20), dann wirst du erkennen, dass bei den bei den ersten ausgegebenen Werten auf den letzten Nachkommastellen ein Unterschied ist, obwohl nur 1 subtrahiert wurde
-
xStrykex schrieb:
http://www.tutorials.de/c-c/381980-problem-gleitkommatypen-assoziativgesetz.html#post1975101
wenn du die nicht geltende assoziativität meinst ist das nichts neues. dass es ungeneuigkeiten gibt beim rechnen mit gleitkomma zahlen lernt men sehr schnell. dass man diesem "phänomen" einen name gibt bzw. es mit fehlender assoziativiät ausdrückt kam bei uns dann im ersten semester in der programmier vorlesung
kleines beispiel:
float x = 2304.0f; float y = -4096.0f; float z = 4096.001953125f; float e1 = x*(y+z); float e2 = x*y + x*z; cout << e1 << " == " << e2 << " (laut der Mathematik!)" << endl;
-
climits schrieb:
die 1.234567e-7 kann ich nachvollziehen.
.. glaub' ich nicht. Ersetze mal Zeile 13 durch
std::cout << b+c+a << std::endl;
-
Hier noch was zum Spielen:
http://www.binaryconvert.com/index.html
-
whuups, wo sind denn die ganzen Antworten hergekommen?
Ich glaub ich muss das dann wirklich erstmal rein binär machen, damit es Sinn ergibt. Danke für die vielen Antworten. Muss ich mal alles durchgehen
-
naja, übertreiben muss mans nicht.
versuch einfach mal die 3.75 binär darzustellen (da reichen 8bit locker aus)
und danach fängst du mal die 3.6 an...
-
SeppJ schrieb:
mantiz schrieb:
Ist jetzt nur eine Vermutung, aber evtl. wird die Addition im letzten Fall einfach wegoptimiert, da sinnlos.
Nein, Fließkommaoperationen können in der Regel nicht wegoptimiert werden, da die Regeln sehr kompliziert sind. Siehe eben dieses Beispiel, wieso das nicht einfach alles weggekürzt werden kann. Es ist schon immer wieder für eine Überraschung gut, wenn gewisse Operationen zur Compilezeit durchgeführt werden (das könnte hier übrigens tatsächlich passieren, wenn man die Optimierungen anschaltet), weil die dann auch gerne mal mit anderer Präzision durchgeführt werden als man das erwarten würde (z.B. mit 64-Bit-Genauigkeit, anstatt mit 80-Bit). Ich habe oben schon genau erklärt, was vermutlich passiert.
Ok, auch wenn es nicht wegoptimiert wird, so ergibt sich durch die rechts-assioziativität von "+", dass erst "b+c" gerechnet wird, was "0" ergibt, also bleibt nur "a" übrig, was von den Auswirkungen her das gleiche ist.

Hab's jetzt nur mal schnell im Taschenrechner nachvollzogen, wenn ich "a+b" auf 8 Nachkommastellen abschneide und anschließend "+c", also "-1" rechne, dann lande ich bei "1,19...e-7". Die Nachkommastellen unterscheiden sich zwar noch, aber mir ist das Ergebnis nah genug dran. Dann würde ich das also auf die rechts-assioziativität schieben.

-
mantiz schrieb:
Hab's jetzt nur mal schnell im Taschenrechner nachvollzogen, wenn ich "a+b" auf 8 Nachkommastellen abschneide und anschließend "+c", also "-1" rechne, dann lande ich bei "1,19...e-7". Die Nachkommastellen unterscheiden sich zwar noch, aber mir ist das Ergebnis nah genug dran. Dann würde ich das also auf die rechts-assioziativität schieben.

Tja, nur leider kommt bei c + b + a auch 1.23457e-07 heraus:
http://ideone.com/RAuLpg
-
Ok, ich gebe auf.

Bei "c + a + b" tritt der "Fehler" übrigens auf: http://ideone.com/OOQr41
-
mantiz schrieb:
Ok, ich gebe auf.

Bei "c + a + b" tritt der "Fehler" übrigens auf: http://ideone.com/OOQr41
Und deswegen muss man immer genau aufpassen, welche Größenordnungen bei Zahlen zu erwarten sind und wie man daher seine Ausdrücke genau aufschreibt. Numerik ist nicht einfach.
-
Hab das Problem mit der numerischen Auslöschung jetzt weitgehend hinterstiegen. Der ganze Rest mit fehlender Assoziativität und so weiter ist dann aber scheinbar doch noch etwas komplizierter.
Um hier noch auf einzelne Posts einzugehen:Werner_logoff schrieb:
climits schrieb:
die 1.234567e-7 kann ich nachvollziehen.
.. glaub' ich nicht. Ersetze mal Zeile 13 durch
std::cout << b+c+a << std::endl;Kommt bei mir wieder das selbe, also 1.234567e-7 raus, was aber auch nicht verwunderlich ist denn auch hier..
mantiz schrieb:
Bei "c + a + b" tritt der "Fehler" übrigens auf: http://ideone.com/OOQr41
Tritt der Fehler bei mir nicht auf, bzw. ich kriege wieder 1.234567e-7 statt 1.19209e-07 ausgegeben. Keine Ahnung woran das liegt. Vielleicht weil ich einen AMD nutze? Mit Rechenarchitektur kenn ich mich nun überhaupt nicht aus.
Wundern tut es mich aber dass es zumindest bei mantiz und auf der Internetseite tatsächlich einen Unterschied macht, ob man nun a+b+c oder c+a+b rechnet. Da wird in einem Fall dann wie SeppJ meinte, wohl das "Rechenwerk" mit 80 bit genutzt (oder SSE, x87...) und im anderem Fall aus irgendwelchen Gründen nicht.