floats mit std::ostream ganz normal formatieren



  • Ich versuche gerade Fließkommazahlen mit ostream ohne wissenschaftliche Notation zu formatieren und ohne unnötige Nullen am Ende. Klingt komisch, ist aber so.

    #include <sstream>
    #include <iostream>
    
    namespace
    {
    	template <class Float>
    	std::string float_to_sane_string(Float value)
    	{
    		std::ostringstream buffer;
    		//gesucht: Optionen für vernünftige Formatierung
    		buffer << value;
    		return buffer.str();
    	}
    
    	void check_sanity(double value, std::string const &expected)
    	{
    		std::string const formatted = float_to_sane_string(value);
    		if (formatted != expected)
    		{
    			std::cerr << "Expected " << expected << ", got " << formatted << '\n';
    		}
    	}
    }
    
    int main()
    {
    	check_sanity(0.0, "0");
    	check_sanity(0.5, "0.5");
    	check_sanity(0.00005, "0.00005");
    	check_sanity(1000000000.0, "1000000000");
    }
    
    Expected 0.00005, got 5e-05
    Expected 1000000000, got 1e+09
    

    fixed deaktiviert zwar die wissenschaftliche Notation, produziert aber zu viele Nullen am Ende:

    buffer << std::fixed << value;
    
    Expected 0, got 0.000000
    Expected 0.5, got 0.500000
    Expected 0.00005, got 0.000050
    Expected 1000000000, got 1000000000.000000
    

    Weiß jemand wie das geht, was ich will?



  • Ich verstehe nicht, wieso du die iostreams verwenden willst.


  • Mod

    template <class Float>
    	std::string float_to_sane_string(Float value)
    	{
    		auto str = std::to_string(value);
    		boost::algorithm::trim_right_if( str, [](char c){ return c == '0'; } );
    		return str;
    	}
    

    Darauf hättest du aber auch locker selber kommen können. 😕

    Edit: to_string generiert übrigens nie die Exponentialnotation, weil to_string mit einer Fließkommazahl als Argument sprintf mit conversion-specifier f bzw. Lf aufruft, welche nur Dezimalnotation generieren.
    Ob man das Anhängen der Nullen auch in den C++ IOStreams unterbinden kann, weiß ich nicht. Ist das in irgendeiner Weise performance-kritisch? Trimmen ist doch fix genug und leicht lesbar?


  • Mod

    unverständlich schrieb:

    Ich verstehe nicht, wieso du die iostreams verwenden willst.

    Dann zeig mal, wie du das mit printf hin bekommst. Da geht das nämlich genau so wenig.

    In dem Satz ist übrigens auch die Antwort für den Threadersteller versteckt: Es geht nicht. Zumindest nicht mit den Standardflags. Die kanonischen Wege, dies zu erreichen sind:
    -Stringmanipulation: Erst lang in einen String ausgeben, dann die Nullen abschneiden. Das kann man auch schick als Funktion schreiben, so dass man etwas wie cout << my_format(value) schreiben kann.
    -Tief in die IOStreams eintauchen, und sich eine passende num_put-Facet schreiben.



  • Hab das just 4 fun mal händisch gemacht:

    #include <sstream>
    #include <iostream>
    #include <cassert>
    #include <cmath>
    
    namespace
    {
        template <class Float>
        std::string float_to_sane_string(Float value)
        {
            typedef unsigned long long Int;
            std::ostringstream buffer;
    
            if(value < 0) buffer << '-';
            value = std::fabs(value);
    
            buffer << Int(value);
            value -= Int(value);
            assert(value < 1);
            if(value == 0)
                return buffer.str();
    
            buffer << '.';
            do
            {
                value *= 10;
                Float newValue = value - unsigned(value);
                if(newValue > 0.99)
                {
                    buffer << (unsigned(value) + 1);
                    break;
                }
    
                buffer << unsigned(value);
                value = newValue;
                assert(value < 1);
            } while(value != 0);
    
            return buffer.str();
        }
    
        void check_sanity(double value, std::string const &expected)
        {
            std::string const formatted = float_to_sane_string(value);
            if (formatted != expected)
            {
                std::cerr << "Expected " << expected << ", got " << formatted << '\n';
            }
        }
    }
    
    int main()
    {
        check_sanity(0.0, "0");
        check_sanity(0.5, "0.5");
        check_sanity(0.00005, "0.00005");
        check_sanity(1000000000.0, "1000000000");
    }
    

    IEEE-754 Geeks: Irgendetwas daran falsch mit der Ausnahme nan etc?
    Ergebnisse sehen recht brauchbar aus bei meinen Tests.



  • Arcoth schrieb:

    template <class Float>
    	std::string float_to_sane_string(Float value)
    	{
    		auto str = std::to_string(value);
    		boost::algorithm::trim_right_if( str, [](char c){ return c == '0'; } );
    		return str;
    	}
    

    Darauf hättest du aber auch locker selber kommen können. 😕

    Edit: to_string generiert übrigens nie die Exponentialnotation, weil to_string mit einer Fließkommazahl als Argument sprintf mit conversion-specifier f bzw. Lf aufruft, welche nur Dezimalnotation generieren.
    Ob man das Anhängen der Nullen auch in den C++ IOStreams unterbinden kann, weiß ich nicht. Ist das in irgendeiner Weise performance-kritisch? Trimmen ist doch fix genug und leicht lesbar?

    Das kommt nicht durch seine Tests da er ja erwartet dass 5.0 zu 5 wird, deine Lösung macht 5. daraus.


  • Mod

    Das kommt nicht durch seine Tests da er ja erwartet dass 5.0 zu 5 wird, deine Lösung macht 5. daraus.

    Das macht es kein bisschen komplizierter

    template <class Float>
    	std::string float_to_sane_string(Float value)
    	{
    		auto str = std::to_string(value);
    		boost::algorithm::trim_right_if( str, [](char c){ return c == '0'; } );
    		if( !str.empty() && str.back() == '.' )
    			str.pop_back();
    		return str;
    	}
    


  • Na klar, wollte es nur anmerken da er ja freundlicherweise sogar Testfälle geliefert hat. 😉


  • Mod

    🙂



  • SeppJ schrieb:

    Es geht nicht. Zumindest nicht mit den Standardflags. Die kanonischen Wege, dies zu erreichen sind:
    -Stringmanipulation: Erst lang in einen String ausgeben, dann die Nullen abschneiden. Das kann man auch schick als Funktion schreiben, so dass man etwas wie cout << my_format(value) schreiben kann.

    Danke für die Auskunft. Ich mache das jetzt mit einem lokalen ostringstream und Abschneiden.
    Das muss auch gar nicht schnell sein. Ich wollte nur die Float-Formatierung nicht neu erfinden.


Anmelden zum Antworten