Problem mit Double to Int Conversion



  • Hi,
    ich hab kleines Verständnis Problem worin der Unterschied bei Zeile 4-5 und 6 liegt in Bezug auf das erhaltene Ergebnis.
    Compiliert unter Linux mit gcc 4.4.5

    int main() {
    	double val1=1.2,val2=100.0,temp=0.0;
    	int erg=0
    	temp=val1*val2;
    	erg=(int)temp; // Ergebnis= 120
    	erg=int(val1*val2); // Ergebnis =119
    	return 0;
    }
    


  • Das ist ja spaßig!

    Ich konnte Dein Problem wie folgt umbasteln:

    #include <iostream>
    using namespace std;
    
    int main() {
        double val2=100.0;
        cout<< int(1.2*val2) <<'\n';//119
        return 0;
    }
    
    #include <iostream>
    using namespace std;
    
    int main() {
        double const val2=100.0;
        cout<< int(1.2*val2) <<'\n';//120
        return 0;
    }
    


  • andios schrieb:

    int main() {
    	double val1=1.2,val2=100.0,temp=0.0;
    	int erg=0
    	temp=val1*val2;
    	erg=(int)temp; // Ergebnis= 120
    	erg=int(val1*val2); // Ergebnis =119
    	return 0;
    }
    

    Arithmetrische Operationen (hier Multiplikation) mit double liefern nie die erwartete exakte Ganzzahl, sondern geringfügig drunter oder drüber. Für diesen Fall ist es nützlich und üblich, mit einem zusätzlichen Wert eps (z.B. eps=0.0001) zu arbeiten. Also erg=int(val1*val2+0.0001) --> Ergebnis 120
    Ein solcher Wert eps ist inbesondere bei Abfragen auf Gleichheit zwingend notwendig.



  • Das das rechnen mit Double keine Exakten Ganzzahl Werte liefert weis ich.
    Was mich halt nur gewundert hat ist das wenn ich das Ergebnis der Multiplikation in einem Doublewert zwischenspeicher und diesen dann in ein int konvertiere was anderes rauskommt als wenn ich das Ergebnis der Multiplikation direkt in einen Int konvertiere.
    Ich bin davon ausgegangen das in beiden Fällen zumindest das Ergebnis gleich ist,
    ob nun falsch oder richtig. 😕



  • andios schrieb:

    Ich bin davon ausgegangen das in beiden Fällen zumindest das Ergebnis gleich ist,
    ob nun falsch oder richtig. 😕

    Vermutung:
    Davon kann man auch ausgehen. Aber hier rechnen zwei verschiedene Leute.
    Mit const hat der Compiler (zur Compilezeit) gerechnet.
    Ohne const hat das Programm (zur Laufzeit) gerechnet.
    Der Compiler muß da keine doubles genommen haben. Was er nimmt, um im Syntax-Baum Double-Konstanten zu repräsentieren, könnte ja auch ein long double sein oder was noch längeres. Und dann gibt es noch Einstellungen, wie die FPU runden soll, vielleicht sind die unterschiedlich zwischen Compiler und dessen Compilat.

    Übrigens hatte ich unter Windows mit MinGW (weiß die Version jetzt nicht) als Anzeige 119/120.
    Unter Linux mit gcc 4.5.2 habe ich 120/120.



  • Compiler hin Compiler her, mit Vermutungen wird nichts erreicht oder eben manchmal überraschendes. Gerade beim Gebrauch von Fliesskomma gilt es, besondere Sorgfalt zu praktizieren. 🕶
    Es wird hier oft geäussert, Fliesskommazahlen seien wegen solcher Seiteneffekte ungenau. Ist aber unsinnig! 😮
    Wir denken dezimal, das System muss aber binär rechnen und der Compiler das ermöglichen. Die Hersteller der Compiler sind nun mal frei, wie sie das konkret machen.



  • volkard schrieb:

    Das ist ja spaßig!

    Ich konnte Dein Problem wie folgt umbasteln:

    #include <iostream>
    using namespace std;
    
    int main() {
        double val2=100.0;
        cout<< int(1.2*val2) <<'\n';//119
        return 0;
    }
    
    #include <iostream>
    using namespace std;
    
    int main() {
        double const val2=100.0;
        cout<< int(1.2*val2) <<'\n';//120
        return 0;
    }
    

    Hast Du hier nicht 100.0 mit 1.2 vertauscht? Wenn ich val2 auf 1.2 setze und die 100.0 in die folgende Zeile setze, kann ich Deine Ergebnisse per GNU C++ Compiler (4.5.1) bestätigen.

    Erklären kann ich sie auch 🙂

    mit dem const wird val2 ein konstanter Ausdruck. Der GCC verwendet für die Auswertung von konstanten Fließkommaausdrücken eine höhere Genauigkeit (über Bibliotheken wie MPFR und co). Mit dem normalem 64Bit IEEE Format wird die 1,2 folgendermaßen approximiert:

    dezimal:
      1,2
    binär:
      1,0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 ...
    auf 53-Bit-Mantisse gerundet:
      1,0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011
    

    Die nächste 64bit IEEE Fließkommazahl ist also ein kleines bisschen kleiner als 1,2.

    Die genauere Fließkommadarstellung für konstante Ausdrücke könnte jetzt derart sein, dass im Gegensatz zu vorher hier aufgerundet wird.

    auf 56-Bit-Mantisse gerundet:
      1,0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010
    

    56 Bits sind nur ein Beispiel. Wahrscheinlich sind's mehr.

    Die 100.0 sind exakt darstellbar. Wir haben also in einem Fall
    119,999...ZEUG
    als Ergebnis und im zweiten Fall
    120,000...ZEUG

    Und das int(.) schneidet eben die Nachkommastellen ab.

    1,2 ist einfach nicht exakt mit endlicher binärer Mantisse darstellbar. Es wird also ggf auf oder abgerundet.



  • @krümelkacker: und was lernt den Fragesteller und uns das nun? 😕
    Die binäre Darstellung interessiert keine Sau! 😮



  • berniebutt schrieb:

    Es wird hier oft geäussert, Fliesskommazahlen seien wegen solcher Seiteneffekte ungenau. Ist aber unsinnig! 😮

    Ich dachte, das hätten wir ausführlich genug geklärt. Schade, dass du bei deiner Definition von Genauigkeit geblieben bist.

    berniebutt schrieb:

    @krümelkacker: und was lernt den Fragesteller und uns das nun?

    Das lehrt uns, wie so ein Fehler auf tiefer Abstraktionsebene zustande kommt. Für Leute, die sich nicht mit "Fliesskommazahlen haben Rundungsfehler" begnügen.



  • Verzeiht mir, dass ich vor dem Klicken auf "Beitrag antworten" nur bis einschließlich volkard's ersten Beitrag gelesen habe. Mich nervt das auch immer, wenn jemand später nochmal das gleiche von sich gibt wie das, was zuvor schon gesagt worden ist. Dennoch stehen in meinem Beitrag Infos drin, die ich für nicht ganz uninteressant hielt (zB dass der GCC Mit Hilfe der MPFR-Bibliothek Constant-Folding mit höherer Genauigkeit durchführt).

    Was das mit Seiteneffekten zu tun habe und wie man hier behaupten kann, dass das Rechnen mit floats nicht ungenau sei, weiß ich allerdings nicht.



  • Das das "genaue" Rechnen mit Fließkommazahlen nicht trivial ist sollte eigentlich jeder halbwegs vernünftige Programmierer wissen.
    Und es gibt schon mehr als genug Threads zu dem Thema.

    Zurück zum eigentlichen Thema da anscheinend die Diskussion von der eigentlichen Frage etwas abgekommen ist. Formulier ich die Frage mal anders.

    Den die eigentliche Frage lautet.
    Warum ist cast_double_to_int(a*b) ungleich a*b=c cast_double_to_int(c) ?

    Antwort:
    Die genaue Ursache kenn ich auch nicht unter c# tritt das Problem nicht ein.
    Ebensowenig wenn man unter c++ statt mit double mit Float rechnet.

    Einzige Ursache die mir dafür einfällt das der Compiler hier irgendwas vermurkst.



  • pseudi schrieb:

    Warum ist cast_double_to_int(a*b) ungleich a*b=c cast_double_to_int(c) ?
    Einzige Ursache die mir dafür einfällt das der Compiler hier irgendwas vermurkst.

    Der Compiler vermurkst hier gar nichts. Intern rechnet er mit höherer Genauigkeit als der Typ. a*b wird zur Compilezeit ausgewertet, daher steht das ergebnis bereits im Programm exakt genug drin. Bei a*b = c Verlangst du vom Compiler, dass das Ergebnis in einen double gequetscht wird. Dabei geht Genauigkeit verloren. int(c) ergibt daher einen anderen Wert.



  • Der Compiler vermurkst hier gar nichts. Intern rechnet er mit höherer Genauigkeit als der Typ. a*b wird zur Compilezeit ausgewertet, daher steht das ergebnis bereits im Programm exakt genug drin. Bei a*b = c Verlangst du vom Compiler, dass das Ergebnis in einen double gequetscht wird. Dabei geht Genauigkeit verloren. int(c) ergibt daher einen anderen Wert.

    314159265358979 dank dir, das mit der Genauigkeit bei der Berechung hat ich nicht bedacht. Dann ergibt das alles auch einen Sinn.

    Frage beantwortet Fall abgeschlossen. 🙂


Log in to reply