Mysteriöses Ergebnis bei einfacher Rechnung.



  • Preisfrage wie lautet das Ergebnis? Bitte mit der Zahl 123456.78 testen.
    Compileroptionen(Visualstudio 2008) wie fp/precise oder strict bringen keinen Unterschied.

    double d=123456.78;
    int  sup=d;
    d=d-sup;
    
    printf("\n%f",d);
    


  • http://ideone.com/8LPKO
    Was ist daran myteriös?



  • bei mir kommt so was raus 0.7799999998.. usw.



  • bin ich da einen Compilerbug aufgesessen?

    double Dummi;
    
    double Result=modf(123456.78,Dummi);
    
    printf"\n %f",Result);
    

    Kann irgendwer das unter Visualstudion 2008 reproduzieren?
    Mit anderen Zahlen tritt das Problem nicht auf.

    Gruß



  • BILL schrieb:

    Mit anderen Zahlen tritt das Problem nicht auf.

    Doch. Und das ist ganz normal. Rundungsfehler, weil double nicht alle Zahlen exakt darstellen kann.



  • Na gut, man sollte aber doch ganz naiv erwarten können,dass wenigstens "modf" 0.78 zurück liefert. Gibt es für das Problem irgendeine Lösung?
    Ich brauche nur den genauen Nachkomma-Anteil.



  • BILL schrieb:

    Na gut, man sollte aber doch ganz naiv erwarten können,dass wenigstens "modf" 0.78 zurück liefert. Gibt es für das Problem irgendeine Lösung?

    Nein. Keine allgemeine Lösung. Nur unendlich viele für Spezialfälle.

    BILL schrieb:

    Ich brauche nur den genauen Nachkomma-Anteil.

    Für Geldbeträge? Rechne mit long long in Cents.



  • Das Problem ist, dass 0.78 als binäre Fließkommazahl nicht genau darstellbar ist. Du läufst in das selbe Problem, das du hast, wenn du 1/3 im Dezimalsystem aufschreiben willst.

    Ganz generell können mit einer Fließkommazahl nach ieee-754 nur rationale Zahlen genau dargestellt werden, die in gekürzter Form eine Zweierpotenz im Nenner haben. Zum Beispiel ist 0.375 = 3/8 genau darstellbar, 0.78 = 39/50 aber nicht. Ferner ist die Mantisse einer Fließkommazahl begrenzt - bei einem double hat man (von esoterischen Systemen mal abgesehen) 53 binäre Nachkommastellen, so dass die Genauigkeit eines doubles x bei etwa x * 10-16 liegt.

    Ob allerdings aus einer Rechnung mit Fließkommazahlen, selbst wenn am Ende bei symbolischer Berechnung ein solches Ergebnis herauskäme, die genaue Zahl herauskommt, ist immer noch zweifelhaft. Beispielsweise wird

    for(double x = 0.0; x != 1.0; x += 0.1);
    

    in der Regel eine Endlosschleife sein, da die Zwischenergebnisse nicht genau dargestellt werden können und 1.0 somit verpasst wird.

    Es ist dementsprechend generell sinnvoll, sich bei Fließkommazahlen nicht auf ganz genaue Ergebnisse zu verlassen. Vergleiche mit == und != sollte man beispielsweise unterlassen und einen Epsilonvergleich benutzen:

    bool epsilon_equal(double x, double y, double eps) {
      return std::abs(x - y) < eps;
    }
    

    Damit ist es aber noch nicht vorbei: Beim Umgang mit Fließkommazahlen in verschiedenen Größenordnungen können diese Rundungsfehler sich leicht in relevante Größenordnungen verschieben. Nimm beispielsweise eine numerische Ableitung:

    double numeric_derivate(std::tr1::function<double(double)> func, double x, double eps) {
      return (func(x + eps) - func(x - eps)) / (2 * eps);
    }
    

    Sei func = sqrt. Dann ist (ich tu jetzt mal so, als sei die interne Darstellung dezimal, um es einfacher lesbar zu machen) mit x = 3 und eps = 1e-12:

    Gewolltes Ergebnis:
     sqrt'(3) = 1 / (2 * sqrt(3))               = 0.2886751345948128
    
    Numerische Näherung:
     sqrt(3 + eps)                              = 1.7320508075691659
                     sqrt(3 - eps)              = 1.7320508075685886
     sqrt(3 + eps) - sqrt(3 - eps)              = 0.0000000000005773 (!)
    (sqrt(3 + eps) - sqrt(3 - eps)) / (2 * eps) = 0.2886500000000000
    

    ...was natürlich eine sehr schlechte Näherung ist. In solchen Situationen muss man sehr vorsichtig bei der Wahl seines Epsilons sein; die selbe Rechnung mit eps = 1e-5, wovon man als Mathematiker (Numeriker ausgenommen) ungenauere Ergebnisse erwarten würde, sieht beispielsweise so aus:

    sqrt(3 + eps)                              = 1.7320536943178176
                     sqrt(3 - eps)              = 1.7320479208151257
    (sqrt(3 + eps) - sqrt(3 - eps))             = 0.0000057735026919
    (sqrt(3 + eps) - sqrt(3 - eps)) / (2 * eps) = 0.2886751345950000
    

    Die richtige Wahl des Epsilons ist natürlich nur ein Baustein in recht komplexer Materie - 10 Stellen Genauigkeit wie in diesem Beispiel mag in den meisten Fällen ausreichen, aber jetzt stell dir vor, du willst numerisch die zweite Ableitung nehmen, und auf einmal hast du ein echtes Problem.

    Einfache Lösungen kann ich leider nicht anbieten - Numerik ist ein weitläufiges Feld, über das ganze Bibliotheken existieren. Höllische Vorsicht ist angebracht.



  • Hallo,

    Hier nochmal was zum Lesen
    http://floating-point-gui.de/



  • seldon schrieb:

    Ganz generell können mit einer Fließkommazahl nach ieee-754 nur rationale Zahlen genau dargestellt werden, die in gekürzter Form eine Zweierpotenz im Nenner haben.

    Danke, der Satz ist Gold wert! 👍


Anmelden zum Antworten