Alles dreht sich ums Geld

C++ kennt zwar keinen Datentypen für Geldbeträge, aber die Localization-Library bietet zumindest die Möglichkeit, diese flexibel auszugeben.

Video

Quellcode

#include <iostream>
#include <locale>
#include <memory>

using namespace std;

template <typename CharT> class BitcoinPunct : public moneypunct<CharT>
{
    ctype<CharT> const &m_ctype;

    typename moneypunct<CharT>::string_type do_widen(string const &src) const
    {
        unique_ptr<CharT[]> out(new CharT[src.size()]);
        m_ctype.widen(src.data(), src.data()+src.size(), out.get());
        return typename moneypunct<CharT>::string_type(out.get());
    }
    
public:
    BitcoinPunct(locale const &loc)
        : m_ctype(use_facet<ctype<CharT>>(loc))
    {
    }
    
protected:    
    
    CharT do_decimal_point() const { return m_ctype.widen(','); }
    CharT do_thousands_sep() const { return m_ctype.widen('.'); }
    string do_grouping() const { return "\003"; }
    typename moneypunct<CharT>::string_type do_curr_symbol() const 
        { return do_widen("BTC"); }
    typename moneypunct<CharT>::string_type do_positive_sign() const 
        { return do_widen("+"); }
    typename moneypunct<CharT>::string_type do_negative_sign() const 
        { return do_widen("-"); }
    int do_frac_digits() const { return 8; }
    money_base::pattern do_pos_format() const { return money_base::pattern {{ 
                                                         money_base::value, 
                                                         money_base::space,
                                                         money_base::symbol,
                                                         money_base::none }}; }
    money_base::pattern do_neg_format() const { return money_base::pattern {{
                                                         money_base::sign,
                                                         money_base::value, 
                                                         money_base::space,
                                                         money_base::symbol }}; }
};

int main()
{
    locale loc("de_DE.UTF-8");    
    cout.imbue(loc);
        
    auto &money = use_facet<money_put<char>>(loc);
    
    cout << showbase;
    money.put(cout, false, cout, ' ', 110023);
    cout << endl;
    cout << noshowbase;
    money.put(cout, false, cout, ' ', 110023);
    cout << endl;
    
    locale bc(locale::classic(), new BitcoinPunct<wchar_t>(locale::classic()));
    wcout.imbue(bc);

    auto &bcmoney = use_facet<money_put<wchar_t>>(bc);
    wcout << showbase;
    bcmoney.put(wcout, false, wcout, L' ', 123456789000);
    wcout << endl;
    bcmoney.put(wcout, false, wcout, ' ', -99999990000);
    wcout << endl;
}

Erklärung

Zur Ausgabe von Geldbeträgen bietet C++ die Facette std::money_put. Da kein eigener Geldtyp vorhanden ist, kennzeichnet man dadurch extra den Wunsch, die betreffende Zahl als Geldbetrag auszugeben. Die eigentliche Zahl repräsentiert dabei immer die kleinste Stückelung (bei der Ausgabe von Euro in Zeile 56 und 59 also Cent), so dass der Betrag immer als ganze Zahl repräsentiert wird. Abhängig von der Einstellung des Flags std::iso_base::showbase im entsprechenden Ausgabestrom wird das Währungssymbol mit ausgegeben oder nicht. Das Währungssymbol und die Darstellung allgemein hängen dabei von der verwendeten Locale ab. In unserem Beispiel wird die deutsche Locale verwendet, als erfolgt die Ausgabe in Euro mit dem Eurosymbol (abhängig von der Qualität der Implementierung der Standardbibliothek. Es ist wie immer nicht vorgeschrieben, dass es die deutsche Locale auch tatsächlich geben muss).

Intern macht std::money_put von std::moneypunct gebrauch, um die notwendigen Informationen über die Währung zu erhalten. Im Beispiel legen wir hier eine neue Facette für Bitcoins an, die von std::moneypunct ableitet. Wie bei den anderen Standardfacetten auch bietet std::moneypunct eine öffentliche Schnittstelle, die mittels einer Handvoll protected virtual Methoden implementiert ist. Das erlaubt uns eine einfache Überladung dieser Methoden, um die Schnittstelle an unsere Bedürfnisse anzupassen. Die Methoden liefern Informationen über Währungssymbol, Gruppierung der Tausenderstellen, Tausender- und Dezimaltrenner etc.

Speziell die beiden Methoden do_neg_format() und do_pos_format() sind für die eigentliche Darstellung noch interessant. US-Dollar werden bspw. oft in der Form $123.23 ausgegeben, während in Deutschland eher die Darstellung 123.23 € üblich ist. Diese Reihenfolge der einzelnen Bestandteile wird über die Formatdefinitionen festgelegt. Hierfür wird ein Array aus vier Elementen zurückgeliefert, welches an jeder Stelle einen passenden Tag enthält. Die Folge sign, value, space, symbol definiert bspw. die typisch deutsche Darstellung "-betrag €".

Um eine allgemeine Verwendbarkeit mit verschiedenen Character Types zu gewährleisten, nutzt BitcoinPunct die std::ctype-Facette der übergebenen Locale. Dadurch können alle Strings intern als char * abgelegt werden, während sie nach außen ggf. auch als std::basic_string<wchar_t> zurückgegeben werden. std::ctype::widen übernimmt die Umwandlung, wo nötig.