Feste Anzahl Nachkommastellen => geht das auch anders?



  • Hallo zusammen,

    ich möchte ein long-value mit einer festen Anzahl an Nachkommastellen ausgeben, wobei die letzten vier Stellen als Nachkommastellen interpretiert werden.

    12345 => 1.2345
    0 => 0.0000
    -10 => -0.0010

    Ich habe das auch soweit erreicht, frage mich aber, ob das nicht einfacher geht?

    std::ostream& operator<<(std::ostream& os, const Currency& currency)
    {
        return os << (currency.value < 0 ? "-" : "") << labs(currency.value) / 10000 << "." << std::setfill('0') << std::setw(4) << labs(currency.value) % 10000;
    }
    

    Das negative Vorzeichen behandle ich separat, weil "%" auch das Vorzeichen berücksichtigt und bei "-1000" dann "0.-1000" rauskommt.



  • #include <iomanip>
    #include <ios>
    #include <iostream>
    
    void test(long l)
    {
        std::cout << l << " => " << std::fixed << std::setprecision(4) << (l / 10000.0) << '\n';
    }
    
    int main()
    {
        test(12345);
        test(0);
        test(-10);
    }
    


  • Ich hab es geahnt...

    std::fixed hatte ich übersehen.

    Super, danke!



  • Nö, der Weg über double ist inkorrekt.

    Beweis:

    root [0] auto l = numeric_limits<long>::max()
    (long) 9223372036854775807
    root [1] cout << l << " => " << std::fixed << std::setprecision(4) << (l / 10000.0) << '\n';
    9223372036854775807 => 922337203685477.6250
    root [2] cout << (l < 0 ? "-" : "") << labs(l) / 10000 << "." << std::setfill('0') << std::setw(4) << labs(l) % 10000 << '\n';
    922337203685477.5807
    

    Whoops!
    Der Sinn von long für currency ist ja gerade, dass man diese floating-point-Probleme nicht bekommt. Wenn man dann durch double teilt, handelt man sie sich wieder ein.



  • Oha, das ist Mist.

    Danke für den Einspruch, wob.

    temi



  • Also ob das via double geht, hängt natürlich davon ab, wie groß long und double bei dir sind. Obige Werte kommen jedenfalls bei mir raus.

    Wenn wir außerdem schon dabei sind, könntest du auch mal schauen, was bei numeric_limits<long>::min() mit deiner Lösung passiert... Vielleicht solltest du das labs erst nach dem Dividieren/Modulo durch 10000 machen 😉



  • Ab C++11 gäbe es passende std::abs Overloads auch für long long bzw. intmax_t .



  • hustbaer schrieb:

    Ab C++11 gäbe es passende std::abs Overloads auch für long long bzw. intmax_t .

    Ich blick irgendwie bei den Bibliotheken nicht mehr durch. 😕

    Hier http://www.cplusplus.com/reference/cmath/abs/ werden nur Gleitpunkttypen erwähnt, aber dafür:

    Since C++11, additional overloads are provided in this header (<cmath>) for the integral types: These overloads effectively cast x to a double before calculations (defined for T being any integral type).

    Hier http://en.cppreference.com/w/cpp/numeric/math/abs werden die oben erwähnten genannt.

    Vielleicht solltest du das labs erst nach dem Dividieren/Modulo durch 10000 machen 😉

    Du hast recht. Mit std::abs(numeric_limits<long>::min()) funktioniert "abs" nicht.

    Edit: Mir lag gerade noch die Frage auf der Zunge: Aber warum?

    Vermutlich liegt es ganz einfach daran, dass max() den Absolutwert von min() nicht darstellen kann, weil sich der positive vom negativen Bereich um genau 1 unterscheiden.



  • Du musst <cstdlib> inkludieren:
    http://www.cplusplus.com/reference/cstdlib/abs/

    Und ja, abs() auf den kleinsten darstellbaren Wert geht normalerweise nicht, da der üblicherweise eins weiter von Null entfernt ist als der grösste darstellbare Wert. -128 ... +127 und so.



  • hustbaer schrieb:

    Du musst <cstdlib> inkludieren:
    http://www.cplusplus.com/reference/cstdlib/abs/

    Und ja, abs() auf den kleinsten darstellbaren Wert geht normalerweise nicht, da der üblicherweise eins weiter von Null entfernt ist als der grösste darstellbare Wert. -128 ... +127 und so.

    Ich habe <cmath> includiert - es funktioniert auch.

    Andere Frage:

    wob hat ja bereits erkannt, dass es sich um eine Klasse handelt, die einen monetären Betrag intern als Festpunktzahl darstellt.

    Als Konstruktorparameter ist allerdings "double" vorgesehen, um

    Currency c = 123.45;
    

    ermöglichen zu können.

    Grundsätzlich ist es ja egal welchen internen Ganzzahltypen ich verwende, ich werde früher oder später immer an eine Grenze stoßen können. Spätestens, wenn zwei "Currencies" multipliziert werden sollen.

    Was ist die sinnvolle Reaktion auf einen Überlauf? Exception?

    "labs" reagiert ja offenbar durch simples nixtun.

    temi



  • Ich hätte noch eine Frage zu dem Zitat von etwas weiter oben:

    Since C++11, additional overloads are provided in this header (<cmath>) for the integral types: These overloads effectively cast x to a double before calculations (defined for T being any integral type).

    Wenn in "abs" zu double gecastet wird, um den Absolutwert zu bilden, habe ich doch wieder ins Klo gegriffen und eine Gleitpunktoperation in meinem Code? Oder verstehe ich diese Aussage falsch?

    Könnte ich einfach das machen, um den Absolutwert zu erhalten, bzw. was macht da der Compiler draus?

    long absl(long i)
    {
        return i >= 0 ? i : -i;
    }
    


  • temi schrieb:

    hustbaer schrieb:

    Du musst <cstdlib> inkludieren:
    http://www.cplusplus.com/reference/cstdlib/abs/

    Und ja, abs() auf den kleinsten darstellbaren Wert geht normalerweise nicht, da der üblicherweise eins weiter von Null entfernt ist als der grösste darstellbare Wert. -128 ... +127 und so.

    Ich habe <cmath> includiert - es funktioniert auch.

    Verlass dich nicht aus sowas. Es sei denn es wäre garantiert dass <cmath> auch <cstdlib> anzieht. Möglich dass das so ist, weiss ich nicht, sowas merk' ich mir nicht. Ich include lieber die Files aus denen ich Funktionen verwende.

    temi schrieb:

    wob hat ja bereits erkannt, dass es sich um eine Klasse handelt, die einen monetären Betrag intern als Festpunktzahl darstellt.

    Als Konstruktorparameter ist allerdings "double" vorgesehen, um

    Currency c = 123.45;
    

    ermöglichen zu können.

    Grundsätzlich ist es ja egal welchen internen Ganzzahltypen ich verwende, ich werde früher oder später immer an eine Grenze stoßen können. Spätestens, wenn zwei "Currencies" multipliziert werden sollen.

    [schwafel]Currencies zu multiplizieren macht keinen Sinn. 🤡 Aber natürlich gibt es genug Fälle wo man Fixkommazahlen multiplizieren möchte, vielleicht sogar nen Geldbetrag mit einer Fixkommazahl die zufällig die gleiche Auflösung haben soll wie der Geldbetrag.[/schwafel]

    Also dafür brauchst du ... entweder nen Integer-Typ eine Nummer grösser. Idealerweise. Dann ist die Implementierung trivial. Wenn du Boost verwenden kannst gibt es da z.B. Boost.Multiprecision, da hast du Integer-Klassen für 128, 256, 512 Bit usw. drinnen.

    Oder, falls du keinen grösseren Integer-Typ hast, brauchst du eine sog. "muldiv" Funktion. Also eine Funktion die a*b/c rechnen kann (alle vom selben typ = der grösste Integer Type) und ein Ergebnis mit wieder dem selben Typ zurückgeben... und das so dass das Ergebnis garantiert mit dem zusammenstimmt was man mit unbeschränktem Wertebereich bekommen hätte - zumindest unter der Voraussetzung dass das Ergebnis in den verwendeten Datentyp passt. Dann ist es wieder easy, a und b sind deine Zahlen und c ist der Scaling-Faktor deines Fixkommatyps.

    So eine "muldiv" Funktion implementiert man sich aber idealerweise nicht selbst. Dummerweise bietet C++ da nix fertiges an.

    temi schrieb:

    Was ist die sinnvolle Reaktion auf einen Überlauf? Exception?

    "labs" reagiert ja offenbar durch simples nixtun.

    Darauf gibt es nicht die Antwort. Oft wird "ignorieren" (bzw. definieren dass es nicht vorkommen darf, bzw. dass das Ergebnis undefiniert ist wenn es doch vorkommt) ein sehr guter Kompromiss sein.

    Wenn man mehr braucht wird es nämlich schnell recht aufwendig.

    Alternativ könnte man natürlich auch eine Big-Integer Klasse mit dynamischer "nur durch den Speicher beschränkter" Grösse verwenden.



  • Du musst <cstdlib> inkludieren

    Ok, ich den Fehler gefunden. Da "NoScript" für www.cplusplus.com die Scripte blockiert hat, habe ich den Tab nicht gesehen, mit dem man zwischen C, C++98 und C++11 umschalten kann und habe demnach nur die C-Version

    int abs (int n);
    

    angezeigt bekommen.

    Currencies zu multiplizieren macht keinen Sinn.

    Du hast Recht. Es geht natürlich darum Currency mit einem Fixkommawert zu multiplizieren.

    Ich denke ich komme mit den "normalen" Festpunkt-Zahlenbereichen zurecht. Falls ich mal so reich werde, dass es nicht mehr langt, kaufe ich mir einen Programmierer 😉

    Oft wird "ignorieren" (bzw. definieren dass es nicht vorkommen darf, bzw. dass das Ergebnis undefiniert ist wenn es doch vorkommt) ein sehr guter Kompromiss sein.

    Finde ich ja bei Finanzberechnungen etwas kritisch. Da sollte man sich schon irgendwie darauf verlassen könne, dass das Ergebnis korrekt ist oder eben klar zeigen, dass es ein Problem gegeben hat.

    Gibt es ein naN auch für Festpunktzahlen?



  • temi schrieb:

    Gibt es ein naN auch für Festpunktzahlen?

    NaN ist grundsätzlich erstmal ein Konzept. Das sich auch auf Fixpoint anwenden lässt. Ob es es dann wirklich gibt, hängt von der konkreten Implementierung ab.


Anmelden zum Antworten