Resource icon

[C++] Nummernformat ändern

Programmiersprache(n)
C++11 oder neuer
Betriebssystem(e)
unspezifisch
Die Implementierung von C++ resultiert standardmäßig in einem Nummernformat mit Punkt als Dezimaltrennzeichen und ohne Gruppierung (Tausendertrennzeichen). Der <locale> Header bietet Möglichkeiten diese Einstellungen auf die der Umgebung des Betriebssystems zu setzen, bei einigen Compilern (wie MinGW/GCC) ist dies aber unvollständig implementiert und funktioniert nicht wie erwartet. Es gibt eine gemeinsame Schnittmenge zur Implementierung in C, die hier herangezogen werden kann. Ebenso ist die Einstellung benutzerdefinierter Werte sehr umständlich.

Im folgenden Header finden sich zwei Funktionen mit unterschiedlichen Überladungen, die diese Einstellungen etwas erleichtern sollen, bzw. Informationen über das derzeitige Nummernformat liefern. Behandelt werden
- das Dezimaltrennzeichen
- das Tausendertrennzeichen
- die Gruppierung (wieviele Ziffern durch das Tausendertrennzeichen zu einer Gruppe zusammengefasst werden)


1) ImbueLocale()
Diese Funktion ändert die Einstellungen von Streamobjekten. Es wird eine Referenz auf das Streamobjekt übergeben, ggf. mit den gewünschten Einstellungen. Die Funktion gibt dann eine Referenz auf das geänderte Streamobjekt zurück.

Es gibt 5 Überladungen von denen 2 am interessantesten sein sollten:
- iosT& ImbueLocale(iosT& io) => Die Einstellungen der Umgebung des Betriebssystems werden übernommen.
- iosT& ImbueLocale(iosT& io, const char& decimal_point) => Ein benutzerdefiniertes Dezimaltrennzeichen wird übernommen.

Nähere Informationen liefern die Kommentare im Header und das Beispiel.


2) GetFormat<format F>()
Diese Funktion gibt (je nach übergebenem Template-Argument aus der format-Enumeration) eine der drei Formatinformationen des übergebenen Streamobjekts zurück. Für das Dezimaltrennzeichen und Tausendertrennzeichen als char, für die Gruppierung als std::string.


numpunct.hpp
C++:
#ifndef NUMPUNCT_HPP_INCLUDED__
# define NUMPUNCT_HPP_INCLUDED__ 1

# include <iosfwd>
# include <string>
# include <locale>
# include <clocale>
# include <type_traits>

  namespace lcnum
  {

    /** \enum format => Contains the values to be passed as template arguments of the GetFormat() function. */
    enum class format{ decimal_point, thousands_sep, grouping };


    /** \fn GetFormat() => Returns the specified format setting of the passed character stream object. */

    /** \overload char GetFormat<format F>(const iosT& io)
     * \brief  => Returns either decimal point or thousands separator depending on the passed template argument.
     * \param  io           Referenced character stream object
     * \return char
     */
    template <format F, typename iosT>
    typename std::enable_if<F != format::grouping, char>::type
      GetFormat(const iosT& io)
    {
      const auto& fct = std::use_facet<std::numpunct<typename std::remove_reference<decltype(io)>::type::char_type>>(io.getloc());
      return static_cast<char>(static_cast<unsigned char>(F == format::decimal_point ? fct.decimal_point() : fct.thousands_sep()));
    }


    /** \overload std::string GetFormat<format F>(const iosT& io)
     * \brief  => Returns the grouping settings if the passed template argument was format::grouping.
     * \param  io           Referenced character stream object
     * \return std::string
     */
    template <format F, typename iosT>
    typename std::enable_if<F == format::grouping, std::string>::type
      GetFormat(const iosT& io)
    {
      return std::use_facet<std::numpunct<typename std::remove_reference<decltype(io)>::type::char_type>>(io.getloc()).grouping();
    }


    namespace detail
    {
      /** \class  impl_numpunct_facet => Derived facet implementation for internal use. */
      template <typename charT>
      struct impl_numpunct_facet : std::numpunct<charT>
      {
        impl_numpunct_facet(const std::string& loc_name)
          : m_unused_init{ *(std::setlocale(LC_NUMERIC, loc_name.c_str())) }
          , m_decimal_point{ static_cast<charT>(static_cast<unsigned char>(*(std::localeconv()->decimal_point))) }
          , m_thousands_sep{ static_cast<charT>(static_cast<unsigned char>(*(std::localeconv()->thousands_sep))) }
          , m_grouping{ std::localeconv()->grouping }
        {
        }

        impl_numpunct_facet(const char& decimal_point, const char& thousands_sep, const std::string& grouping)
          : m_unused_init{}
          , m_decimal_point{ static_cast<charT>(static_cast<unsigned char>(decimal_point)) }
          , m_thousands_sep{ static_cast<charT>(static_cast<unsigned char>(thousands_sep)) }
          , m_grouping{ grouping }
        {
        }

      protected:
        virtual charT do_decimal_point()  const override { return m_decimal_point; }
        virtual charT do_thousands_sep()  const override { return m_thousands_sep; }
        virtual std::string do_grouping() const override { return m_grouping; }

      private:
        const char m_unused_init; // Exists in order to be able to call setlocale() in the member initialization list.
        const charT m_decimal_point, m_thousands_sep;
        const std::string m_grouping;
      };

    } // namespace detail


    /** \fn ImbueLocale() => Changes numeric settings of character stream objects (such as std::ios or std::wios objects). */

    /** \overload iosT& ImbueLocale(iosT& io, const std::string& loc_name)
     * \brief  => Uses the locale settings of the passed locale name.
     * \param  io             Referenced character stream object to be imbued
     * \param  loc_name       Referenced locale name (pass "C" to reset the locale implementation)
     * \return io
     */
    template <typename iosT>
    iosT& ImbueLocale(iosT& io, const std::string& loc_name)
    {
      io.imbue(std::locale(io.getloc(), new detail::impl_numpunct_facet<typename std::remove_reference<decltype(io)>::type::char_type>(loc_name)));
      return io;
    }


    /** \overload iosT& ImbueLocale(iosT& io)
     * \brief  => Uses the locale settings of the environment.
     * \param  io             Referenced character stream object to be imbued
     * \return io
     */
    template <typename iosT>
    iosT& ImbueLocale(iosT& io) { return ImbueLocale(io, ""); }


    /** \overload iosT& ImbueLocale(iosT& io, const char& decimal_point, const char& thousands_sep, const std::string& grouping)
     * \brief  => Uses the passed decimal point, thousands separator, and grouping.
     * \param  io             Referenced character stream object to be imbued
     * \param  decimal_point  Referenced character used as decimal point
     * \param  thousands_sep  Referenced character used as thousands separator
     * \param  grouping       Referenced string used to specify the grouping (e.g. pass "\3" for a grouping of 3 digits each)
     * \return io
     */
    template <typename iosT>
    iosT& ImbueLocale(iosT& io, const char& decimal_point, const char& thousands_sep, const std::string& grouping)
    {
      io.imbue(std::locale(io.getloc(), new detail::impl_numpunct_facet<typename std::remove_reference<decltype(io)>::type::char_type>(decimal_point, thousands_sep, grouping)));
      return io;
    }


    /** \overload iosT& ImbueLocale(iosT& io, const char& thousands_sep, const std::string& grouping)
     * \brief  => Uses the passed thousands separator and grouping. The current decimal separator remains unchanged.
     * \param  io             Referenced character stream object to be imbued
     * \param  thousands_sep  Referenced character used as thousand separator
     * \param  grouping       Referenced string used to specify the grouping (e.g. pass "\3" for a grouping of 3 digits each)
     * \return io
     */
    template <typename iosT>
    iosT& ImbueLocale(iosT& io, const char& thousands_sep, const std::string& grouping) { return ImbueLocale(io, GetFormat<format::decimal_point>(io), thousands_sep, grouping); }


    /** \overload iosT& ImbueLocale(iosT& io, const char& decimal_point)
     * \brief  => Uses the passed decimal point. Thousands separator and grouping are switched off.
     * \param  io             Referenced character stream object to be imbued
     * \param  decimal_point  Referenced character used as decimal point
     * \return io
     */
    template <typename iosT>
    iosT& ImbueLocale(iosT& io, const char& decimal_point) { return ImbueLocale(io, decimal_point, '\0', ""); }

  } // namespace lcnum

#endif // NUMPUNCT_HPP_INCLUDED__
Beispiel
C++:
#include <iostream>
// #include <fstream>
#include <iomanip>
#include "numpunct.hpp"

int main()
{
  double dbl{};

  // hier werden die numerischen Einstellungen der Umgebung auf das cout Objekt übernommen, auch wenn sie noch gar nicht benötigt werden.
  lcnum::ImbueLocale(std::cout) << "Eingabe Fliesskommazahl (mit Komma als Dezimaltrennzeichen): " << std::flush;
  // um diese Einstellungen auch für die Eingabe wirksam werden zu lassen, müssen sie für cin übernommen werden (kann auch gesondert erfolgen)
  lcnum::ImbueLocale(std::cin);
  std::cin >> dbl;

  // einmal auf das Streamobjekt uebernommen, bleiben die Einstellungen wirksam
  std::cout << std::fixed << std::setprecision(2) << dbl << std::endl;
  // Komma als Dezimaltrennzeichen (keine Zifferngruppierung)
  lcnum::ImbueLocale(std::cout, ',') << std::fixed << std::setprecision(2) << dbl << std::endl;
  // Zifferngruppierung mit Leerzeichen in Dreierblöcken
  lcnum::ImbueLocale(std::cout, ' ', "\3") << std::fixed << std::setprecision(2) << dbl << std::endl;
  // mit Übergabe von "C" kann auf die ursprünglichen Einstellungen der Implementierung zurückgesetzt werden
  lcnum::ImbueLocale(std::cout, "C") << std::fixed << std::setprecision(2) << dbl << std::endl;

  // Ausgabe zu fstream Objekt
//   std::ofstream testfile("test.txt");
//   if (testfile.is_open())
//     lcnum::ImbueLocale(testfile) << std::fixed << std::setprecision(2) << dbl << std::endl;

  return 0;
}
Mögliche Ausgabe
Code:
Eingabe Fliesskommazahl (mit Komma als Dezimaltrennzeichen): 2000000,3
2.000.000,30
2000000,30
2 000 000,30
2000000.30
Autor
German
First release
Last update
Bewertung
0,00 Stern(e) 0 Bewertungen
Oben