Double aufrunden



  • int i = 399;

    Umwandlung in double

    double x = (double) 399 / 100.0;
    Ergebnis: x = 3.9900000000000002

    Ich möchte aber 3,99 als Ergebnis haben

    Habe folgendes ausprobiert, jedoch ohne Erfolg

    double x = (int)(x*100+0.5)/100.0;

    Ergebnis: x = 3.9900000000000002

    Hat jemand bitte einen Tipp für mich?



  • Beschränke dich bei der Ausgabe mittels setprecision auf weniger Stellen. Ein komplett exaktes Ergebnis wirst du nicht erhalten.



  • Ja, habe setPrecision ausprobiert. Das funktioniert auch. Aber das Problem ist, ich brauche das Ergebnis in double. Wenn ich den String-Wert, den ich nach setPrecision erhalte, wieder in double konvertiere, becomme ich schon wieder zig Nachkommastellen. Hilfe!!



  • Das Problem ist, dass double die Zahl 3.99 nicht exakt darstellen kann. double kann nur Zahlen exakt darstellen, bei denen der gekürzte Bruch im Nenner nur 2 als Primfaktoren enthält. Genau wie du im Dezimalsystem nur Zahlen darstellen kannst, bei denen der Nenner nur die Primfaktoren 2 und 5 enthält. Das ist bei 399/100 gegeben (100 = 2²*5²).

    Entweder, du rundest deine Zahl jedes Mal vor der Ausgabe oder du stellst sie irgend wie anders dar (Bruch usw.).





  • Wenn du 3.99 lediglich für eine Gleichheitsprüfung benötigst, dann solltest du (wie bei generell bei Fließkommazahlen) einen eps-Bereich betrachten:

    double f = 3.99;
    if( fabs(f-3.99)<1.E-6 ) ...
    

    Eps sollte hier von der Genauigkeit des Datentyps abhängen, den du über <limits> erhältst.



  • Du willst nicht nur die Ausgabe, sondern auch die Zahl selbst gerundet haben. Dazu brauchst du eine Funktion round. Wenn dein Compiler diese nicht bereitstellt, lade sie von meiner Homepage http://berniebutt.npage.de herunter.



  • 3,99 ist ein Beispiel einer Zahl, die nicht als Binärzahl mit endlich vielen Stellen darstellbar ist. Da kommt irgendeine Periode raus, genauso wie bei 1/3 = 0,333333.....

    Angenommen, bei double handelt es sich um IEEE-754 64-Bit Fließkommazahlen.
    Sei F die Menge der per double darstellbaren Zahlen.
    Sei Sd die Menge der Zahlen, die man mit maximal d signifikanten Dezimalstellen darstellen kann.

    Mögliche, verlustfreie Rundreisen:

    (1) Sd -> F -> Sd (mit d <= 15)

    (2) F -> Sd -> F (mit d >= 17)

    ...wobei das -> hier jeweils eine Zuordnung ist, die aus der Zielmenge die Zahl pickt, die am nächsten dran liegt. (Ich ignoriere hier jetzt, dass es zwei solcher Zahlen geben kann.)

    Die Zahl, die dabei in der "Mitte" rauskommt, mag leicht verfälscht sein, aber am Ende hat man wieder dieselbe Zahl, mit der man angefangen hat. Dazu muss nämlich nur die erste Abbildung injektiv sein.

    Beispiel am Zahlenstrahl

    3,5
    ---*------*------*------*------*------*--
       /       \     |     /       \      /      (Erste Konvertierung)
    --x----x----x----x----x----x----x----x---
       \    \  /     |     \  /    /      \      (Zweite Konvertierung)
    ---*------*------*------*------*------*--
                    3,5
    
    * = Element der ersten Menge
    x = Element der zweiten Menge
    

    Die "Auflösung" der zweiten Menge muss nur hoch genug sein (also die Abstände benachbarter Elemente auf dem Zahlenstrahl müssen klein genug sein), damit die Rundreise verlustfrei funktionieren kann.

    kk



  • berniebutt schrieb:

    Du willst nicht nur die Ausgabe, sondern auch die Zahl selbst gerundet haben. Dazu brauchst du eine Funktion round. Wenn dein Compiler diese nicht bereitstellt, lade sie von meiner Homepage http://berniebutt.npage.de herunter.

    Deine grauenhafte round-Funktion regt mich schon seit längerem auf, soweit nix Neues. Aber hier passt sie sowas von gar nicht. Überleg doch bitte fünf Sekunden, bevor du postest.



  • Michael E. schrieb:

    berniebutt schrieb:

    Du willst nicht nur die Ausgabe, sondern auch die Zahl selbst gerundet haben. Dazu brauchst du eine Funktion round. Wenn dein Compiler diese nicht bereitstellt, lade sie von meiner Homepage http://berniebutt.npage.de herunter.

    Deine grauenhafte round-Funktion regt mich schon seit längerem auf, soweit nix Neues. Aber hier passt sie sowas von gar nicht. Überleg doch bitte fünf Sekunden, bevor du postest.

    #include <stdlib.h>
    #include <math.h>

    double round(double value,unsigned short np)
    // ---------------------------------------------------------------------------
    // runden eines double Wertes auf np Nachkommastellen
    // ---------------------------------------------------------------------------
    // value double Wert
    // np Anzahl der gewünschten Nachkommastellen
    // Rückgabe gerundeter Wert
    // ---------------------------------------------------------------------------
    // Beispiel:
    // value = 123.6678923
    // np = 2
    // Rückgabe = 123.67
    // ---------------------------------------------------------------------------
    {
    double factor;
    factor = pow10(np);
    return floor(value*factor+0.5)/factor;
    }

    Was bitte ist an der Funktion round grauenhaft? 😕
    Die Standardfunktionen pow10 und floor machen genau das, wonach hier gefragt war! 😃



  • Ich habe mal Schritt für Schritt nachgerechnet...

    goldenBoy schrieb:

    int i = 399;

    Umwandlung in double

    double x = (double) 399 / 100.0;

    Die Zahl
       dezimal:  3,99
    entspricht                                             ____________________
         binär: 11,111111010111000010100011110101110000101000111101011100001010...
    Ein double kann aber nur die 53 signifikanten Bits speichern. (*)
    Rundet man auf 53 Bits sind das dann:
         binär: 11,111111010111000010100011110101110000101000111101100
    und das sind dann dezimal aber
       dezimal:  3,990000000000000213...
    weil wir vorhin aufgerundet haben.
    

    goldenBoy schrieb:

    x speichert hier die Zahl

    Ergebnis: x = 3.9900000000000002

    Nein. Das tut es nicht. Das ist wieder nur eine auf 17 Stellen gerundete Darstellung der Zahl, die x tatsächlich speichert. Diese Darstellung (mit 17 Stellen) ist aber genau genug, dass wir daraus wieder einen double-Wert erzeugen können, der derselbe ist, den x im Moment speichert, nämlich 3,990000000000000213....

    goldenBoy schrieb:

    Ich möchte aber 3,99 als Ergebnis haben

    Dann verwende höchstens 15 Dezimalstellen bei Konvertierung der double-Zahl in eine Zeichenkette.

    (*: Hierbei nehme ich das IEEE-754 64Bit-Format an und betrachte nur normalisierte Zahlen. Das Format ist üblich, aber nicht vom C++ Standard her festgelegt.)



  • berniebutt schrieb:

    Michael E. schrieb:

    berniebutt schrieb:

    Du willst nicht nur die Ausgabe, sondern auch die Zahl selbst gerundet haben. Dazu brauchst du eine Funktion round. Wenn dein Compiler diese nicht bereitstellt, lade sie von meiner Homepage http://berniebutt.npage.de herunter.

    Deine grauenhafte round-Funktion regt mich schon seit längerem auf, soweit nix Neues. Aber hier passt sie sowas von gar nicht. Überleg doch bitte fünf Sekunden, bevor du postest.

    [Code-Snip]
    Was bitte ist an der Funktion round grauenhaft? 😕

    Ach, hast du dein Monster mittlerweile mal ausgetauscht? Da hab ich ein ganz anderes Konstrukt von dir in Erinnerung: http://www.c-plusplus.net/forum/276468-24
    Ist aber schon dreist von dir, mir meine eigene Implementierung verkaufen zu wollen. Wobei ich keine Funktion namens pow10 benutzt habe, die es im Standard nicht gibt.

    Die Standardfunktionen pow10 und floor machen genau das, wonach hier gefragt war! 😃

    Wie gesagt: pow10 ist keine Standardfunktion. Außerdem bringt deine Funktion hier rein gar nichts. Wie willst du durch Runden der Fließkommazahl eine höhere Genauigkeit verpassen? Lies doch bitte mal, was krümelkacker geschrieben hat, oder beschäftige dich insgesamt mit Fließkommazahlen. Vielleicht hörst du dann auch auf, Fließkommazahlen für Geldbeträge zu verteidigen.



  • berniebutt schrieb:

    Was bitte ist an der Funktion round grauenhaft? 😕

    Sie hält nicht das, was sie verspricht, da bei der Division wieder Rundungsfehler auftreten werden. So bekommst Du wieder nur 3.9900000000000002 bei 17 Dezimalstellen angezeigt. Es löst das Problem also nicht.



  • Ok. miteinander: Dann erweitern wir die Funktion round, auf die Michael E die Urheberschaft deklariert (*), wie folgt:

    double round(double value,unsigned short np)
    // ---------------------------------------------------------------------------
    {
    double x,factor,result;
    factor = pow10(np);
    x = floor(value*factor+0.5)*factor;
    result = floor(x/factor);
    return result;
    }

    ... und der Fisch ist mit einem weiteren floor gegessen! 😮

    Mit Genauigkeit bei floting point hat das alles nichts zu tun. Die bleibt weiterhin compilerabhängig auf z.B. 17 Stellen begrenzt, was gewöhnlich voll ausreicht. Etwas mehr gibt es mit long double statt double. Auch floating point wird intern binär gerechnet und nicht dezimal. Die vermeintliche Ungenauigkeit oder der Schrott in den letzten Stellen stören nur Erbsenzähler! Ob das nun pow oder pow10 heisst ist auch egal. Man nimmt das, was die Implementierung des eigenen Compilers dafür vorsieht.

    (*) Ich dachte, wir helfen uns in diesem Forum gegenseitig und werfen uns keine Plagiatsvorwürfe um die Ohren. 😃



  • Du7 hast anscheinend immer noch nicht kapiert, wo der TE jetzt sein Problem hat - er will den Wert 3,99 exakt speichern, und das ist mit double nunmal prinzipbedingt nicht möglich. Daran ändert sich auch nichts, wenn du noch zehnmal die floor()-Funktion über den Wert drüberjagst.



  • CStoll schrieb:

    Du7 hast anscheinend immer noch nicht kapiert, wo der TE jetzt sein Problem hat - er will den Wert 3,99 exakt speichern, und das ist mit double nunmal prinzipbedingt nicht möglich. Daran ändert sich auch nichts, wenn du noch zehnmal die floor()-Funktion über den Wert drüberjagst.

    Das Problem ist, dass er eine Vorgabe gemacht hat, die so schlicht nicht erfüllbar ist: 3,99 exakt mit double speichern. Irgendwo muss etwas geändert werden. Entweder, man ändert den Datentyp, oder man ändert alle Ausgaben/Rechnungen, bei denen diese Genauigkeit benötigt wird. Da er sich dazu bisher nicht geäußert hat, denke ich, es gibt hier vorerst nicht mehr zu sagen.



  • berniebutt: Ich fass mal für dich zusammen: Der OP hat 399 / 100.0 ausgerechnet und sich gewundert, dass nicht exakt 3.99 rauskommt. Dann willst du ihm helfen, indem du mit 10^x multiplizierst und durch 10^x dividierst. Wo ist da der Unterschied? Wie soll double nun auf wundersame Weise den Wert 3.99 exakt speichern können? Manuell auf die letzte signifikante Ziffer zu runden macht keinen Sinn.


Log in to reply