Wie funktioniert Konvertierung von double zu int?



  • Guten Morgen!

    Kurz zu meiner Problemstellung:
    Ich lese eine Datei mit vielen (Geld)Beträgen. Die Beträge haben zwei zu berücksichtigende Nachkommastellen und sind in der Datei als double-Werte gespeichert.
    Die Beträge müssen aufaddiert werden.
    Dabei kommt es wegen der internen binären Speicherung der Fließkommazahlen zu Rundungsproblemen. Deshalb will ich jeden einzelnen Betrag erst Mal in einen Integer - Wert konvertieren, damit die Addition durchführen, und für die Ausgabe des Ergebnisses wieder das Komma dazwischenfummeln.

    Aber das erwähnte Rundungsproblem (ich nehme an, daß es das ist), macht mir auch bei ganz normaler Konvertierung zu schaffen:

    #include <sstream>
    #include <iostream>
    #include <iomanip>
    
    using namespace std;
    
    int main()
    {
    	double db;
    	int intg;
    
    	db = 143.76;
    	intg = (int)(db * 100);
    	cout << db << '\t' << intg << endl; //ergibt: 143.76  14375 --> falsch
    
    	//Aber:
    	cout << db * 100 << '\t' << intg << endl; //Der Double-Wert ist noch korrekt
    
    	//Stattdessen double - Wert in einen String schreiben
    	stringstream sstr;
    
    	sstr << fixed << setprecision(2) << db * 100;
    	string str = sstr.str();
    
    	//Aus dem String den Dezimalpunkt löschen
    	str.erase(str.size() - 3);
    
    	//Den String in einen Integer schreiben
    	sstr.str(str);
    	sstr >> intg;
    
    	cout << db << '\t' << intg << endl; //ergibt: 143.76  14376 --> korrekt
    }
    

    Ich war bisher der Meinung, daß bei Zuweisung eines double - Wertes an einen int - Wert einfach die Nachkommastellen weggelassen werden.
    Das sieht hier allerdings nicht so aus und wundert mich doch sehr ...
    Gibt es evtl. eine einfachere Möglichkeit, als den Weg über den String zu gehen?



  • Addiere einfach ein epsilon auf den double-Wert. Da bei dir nur Werte vorkommen, die nicht näher als 0.01 aneinanderliegen, kannst du z. B. 0.001 nehmen.



  • Michael E. schrieb:

    Addiere einfach ein epsilon auf den double-Wert.

    ein 'epsilon' reicht i.A. nicht - einigermaßen korrekt wäre:

    double db = 143.76;
        int intg = int( std::floor(db * 100 + 0.5) ); // std::floor erfordert #include <cmath>
    


  • Werner_logoff schrieb:

    Michael E. schrieb:

    Addiere einfach ein epsilon auf den double-Wert.

    ein 'epsilon' reicht i.A. nicht

    Hab ja nur von diesem Fall geredet.

    [Edit] Solange die Zahlen nicht negativ sind.



  • Mhm, verstehen tue ich das leider nicht.
    Allerdings funktioniert bei meinen Experimenten (auf Grund des ersten Antwortpostings):

    int = (int) ((double + 0.001) * 100);
    

    für 10.00 <= double <= 2000.00

    Erst Mal Danke schön an Euch!



  • Bitte keinen alten C-Cast verwenden sondern C++ benutzen mit

    int = static_cast<int> ((double + 0.001) * 100);
    


  • Geldbeträge mit double zu verwenden hat seine Tücken, besonders wenn wie bei Zins- oder Mehrwertsteuer-Bberechnungen 'krumme' Beträge herauskommen und diese vielleicht noch addiert werden. Da enstehen leicht Unstimmigkeiten.

    Alternativen:
    1. Mit longint in Cent rechnen
    2. Mit double rechnen und berechnete Werte mit einer Funktion an der 3. Stelle hinter dem Dezimalpunkt auf- oder abrunden.

    Ich setze mit double zwei eigene Funktionen runden() und dtoa() für die Ausgabe ein und habe damit alles im Griff. Wer will, kann diese Funktionen per E-Mail bei mir anfordern.



  • Ja, deshalb konvertiere ich ja vor dem Addieren in int (ist zumindest mein Plan). Und Runden an der dritten Stelle hinterm Komma kann doch nach zB

    double zahl = 123.45;
    

    nicht viel Sinn machen, oder.
    Die Werte stammen alle aus Benutzereingaben, bei denen sichergestellt ist, daß sie nicht mehr als zwei Nachkommastellen haben.
    Sie sind halt blöderweise in der Datei als double abgelegt.

    Sicher wäre es am besten, in dem ganzen Verfahren überall auf int oder long long umzustellen, vielleicht mache ich mir irgendwann mal die Arbeit ...



  • Wenn alles schon auf 2 Nachkommastellen vorliegt, ist doch alles klar. Wozu brauchst du dann noch eine Konvertierung auf int? Bedenke: alle Fliesskommzahlen wie z.B. dezimal 123.45 werden als Mantisse und Exponent auf der Basis 2 dargestellt. Für jede Umwandlung in einen anderen Datentyp wie int oder char braucht man ein eps oder spezielle nicht standardisierte Funktionen.



  • berniebutt schrieb:

    Wenn alles schon auf 2 Nachkommastellen vorliegt, ist doch alles klar. Wozu brauchst du dann noch eine Konvertierung auf int?

    Die Binärdarstellung der Fließkommazahlen ist nicht immer eindeutig. So kann beispielsweise der Dezimalwert 0,1 binär nur als periodische Zahl gespeichert werden. Wenn man mit solchen Zahlen viele Rechenoperationen durchführt, kommt es daher irgendwann zu Rundungsfehlern.
    Probier zum Beispiel mal:

    double t = 0.1;
       for(int i = 0; i < 10000; ++i)
       	t += 0.1;
    
       cout << "t = " << t << endl;
    

    Aus dem Grund will ich die Zahlen in int-Werte konvertieren, bevor ich damit rechne ...

    Edit: Ne, das Beispiel zeigt irgendwie nicht, was ich wollte, ich muß nochmal experimentieren ...

    Edit:
    Neues Beispiel:

    double t = 0.1;
    
       for(int i = 0; i < 100; ++i)
       	t += 0.1;
    
       cout << "t = " <<  fixed << setprecision(15) << t << endl;
    

    Ausgabe:

    t = 10.099999999999980
    


  • Schau dir das mal an, das hat nichts mit dem Rechnen zu tun. Der Fehler liegt wohl woanders...

    double t = 0.1; 
    	double test = 0.123456789123456789123456789;
    
    	for(int i = 0; i < 100; ++i) 
    	   t += 0.1; 
    
    	cout << "t = " <<  fixed << setprecision(30) << t << endl;
    	cout << "test = " <<  fixed << setprecision(30) << test << endl;
    

    Ergebnis:

    t = 10.099999999999980000000000000000
    test = 0.123456789123456780000000000000



  • Es liegt wie gesagt daran, daß im Binärsystem andere Zahlen periodisch dargestellt werden müssen, als im Dezimalsystem. Zum Beispiel:

    double t2 = 1.0 / 3.0;
       cout <<  fixed << setprecision(40) << t2 * 3 << endl;
    

    1/3 ist im Dezimalsystem nicht endlich darstellbar, im Binärsystem schon, die Ausgabe ist:

    1.0000000000000000000000000000000000000000
    

    Und wenn man nun mit Fließkommazahlen wie zB 0.1 rechnet, die in der Binärdarstellung eben nicht genau dem Dezimalwert 0.1 entsprechen, dann kann es zu Rundungsfehlern kommen.

    Deshalb sollte man in Anwendungen, die mit Währungsbeträgen umgehen, auf gar keinen Fall mit Fließkommazahlen arbeiten.



  • Belli schrieb:

    1/3 ist im Dezimalsystem nicht endlich darstellbar, im Binärsystem schon

    Nö, auch da ist das nichtabbrechend.



  • Mhm, da hab ich mich wohl geirrt. Aber entscheidend für die Problematik bleibt die Tatsache, daß es eine Reihe endlicher Dezimalzahlen gibt (zB 0.1), die im Binärsystem nicht endlich darstellbar sind.



  • Ich habe mal eine Klasse geschrieben die die Grundrechenarten "* / + -" beherrscht und Dezimalzahlen ohne Rundungsfehler berechnen kann. Vielleicht hilft es ja jmd eine bessere Klasse als meine zu schreiben. MFG

    main.cpp

    #include <iostream>
    #include <iomanip>
    #include "dezimal.h"
    
    using namespace std;
    
    int main()
    {
    	//Dezimal a(1.7);
    	//Dezimal b(2.6);
    	//Dezimal d(2.6);
    	//Dezimal c;
    
    	//c = (b + a) * d;
    	//c = (b - a) / d;
    	//c = (b - a) * d;
    	//c = (b + a) / d;
    	//c = b - a * d;
    	//c = b - a / d;
    	//c = b + a * d;
    	//c = b + a / d;
    
    	//cout << a.getDouble() << endl;
    	//cout << b.getDouble() << endl;
    	//cout <<  fixed << setprecision(15) << c.getDouble() << endl;
    
    	Dezimal t = 0.1; 
    
        for(int i = 0; i < 100; ++i) 
           t = t + 0.1; 
    
        cout << "t = " <<  fixed << setprecision(15) << t.getDouble() << endl;
    
    	cin.get();
    	cin.get();
    
    	return 0;
    }
    

    Dezimal.h

    class Dezimal
    {
    private:
    	double value;
    
    public:
    	Dezimal(double dezimal): value(dezimal){}
    	Dezimal(){}
    
    	double getDouble(){return value;}
    
    	Dezimal operator=(const Dezimal &rhs);
    	Dezimal operator+(const Dezimal &rhs); 
        Dezimal operator*(const Dezimal &rhs); 
        Dezimal operator/(const Dezimal &rhs); 
        Dezimal operator-(const Dezimal &rhs);
    };
    
    int KommastellenBerechnen(const double &dezimal);
    double KommastellenEntfernen(const double &dezimal, const int &kommastellen);
    double KommastellenHinzufuegen(const double &dezimal, const int &kommastellen);
    

    Dezimal.cpp

    #include "dezimal.h"
    #include <string>
    #include <sstream>
    
    Dezimal Dezimal::operator=(const Dezimal &rhs){
    	this->value = rhs.value;
    	return *this;
    }
    
    Dezimal Dezimal::operator+(const Dezimal &rhs){
    	double tempLhs = this->value;
    	double tempRhs = rhs.value;
    	int tempstellen;
    
    	if(KommastellenBerechnen(this->value) >= KommastellenBerechnen(rhs.value))
    		tempstellen = KommastellenBerechnen(this->value);
    	else
    		tempstellen = KommastellenBerechnen(rhs.value);
    
    	tempLhs = KommastellenEntfernen(tempLhs, tempstellen);
    	tempRhs = KommastellenEntfernen(tempRhs, tempstellen);
    
    	Dezimal result;
    	result.value = tempLhs + tempRhs;
    
    	result.value = KommastellenHinzufuegen(result.value, tempstellen);
    
    	return result;
    }
    
    Dezimal Dezimal::operator*(const Dezimal &rhs){
    	double tempLhs = this->value;
    	double tempRhs = rhs.value;
    	int tempstellen;
    
    	if(KommastellenBerechnen(this->value) >= KommastellenBerechnen(rhs.value))
    		tempstellen = KommastellenBerechnen(this->value);
    	else
    		tempstellen = KommastellenBerechnen(rhs.value);
    
    	tempLhs = KommastellenEntfernen(tempLhs, tempstellen);
    	tempRhs = KommastellenEntfernen(tempRhs, tempstellen);
    
    	Dezimal result;
    	result.value = tempLhs * tempRhs;
    
    	result.value = KommastellenHinzufuegen(result.value, tempstellen * 2);
    
    	return result;
    }
    
    Dezimal Dezimal::operator/(const Dezimal &rhs){
    	double tempLhs = this->value;
    	double tempRhs = rhs.value;
    
    	if(rhs.value != 0)
    	{
    		int tempstellen;
    
    		if(KommastellenBerechnen(this->value) >= KommastellenBerechnen(rhs.value))
    			tempstellen = KommastellenBerechnen(this->value);
    		else
    			tempstellen = KommastellenBerechnen(rhs.value);
    
    		tempLhs = KommastellenEntfernen(tempLhs, tempstellen);
    		tempRhs = KommastellenEntfernen(tempRhs, tempstellen);
    
    		Dezimal result;
    		result.value = tempLhs / tempRhs;
    
    		result.value = KommastellenHinzufuegen(result.value, tempstellen / 2);
    
    		return result;
    	}
    
    	return rhs;
    }
    
    Dezimal Dezimal::operator-(const Dezimal &rhs){
    	double tempLhs = this->value;
    	double tempRhs = rhs.value;
    	int tempstellen;
    
    	if(KommastellenBerechnen(this->value) >= KommastellenBerechnen(rhs.value))
    		tempstellen = KommastellenBerechnen(this->value);
    	else
    		tempstellen = KommastellenBerechnen(rhs.value);
    
    	tempLhs = KommastellenEntfernen(tempLhs, tempstellen);
    	tempRhs = KommastellenEntfernen(tempRhs, tempstellen);
    
    	Dezimal result;
    	result.value = tempLhs - tempRhs;
    
    	result.value = KommastellenHinzufuegen(result.value, tempstellen);
    
    	return result;
    }
    
    int KommastellenBerechnen(const double &dezimal){
    	std::stringstream NumberString;
    	NumberString << dezimal;
    	std::string strzahl = NumberString.str();
    
    	if(strzahl.find(".") != -1)
    		return strzahl.length() - strzahl.find(".") - 1;
    	else
    		return 0;
    }
    
    double KommastellenEntfernen(const double &dezimal, const int &kommastellen){
    	if(kommastellen > 0)
    	{
    		double multiplikator = 1;
    
    		for(int i = 0; i < kommastellen; ++i)
    			multiplikator = multiplikator * 10;
    
    		return dezimal * multiplikator;
    	}
    
    	return dezimal;
    }
    
    double KommastellenHinzufuegen(const double &dezimal, const int &kommastellen){
    	if(kommastellen > 0)
    	{
    		double multiplikator = 1;
    
    		for(int i = 0; i < kommastellen; ++i)
    			multiplikator = multiplikator * 10;
    
    		return dezimal / multiplikator;
    	}
    
    	return dezimal;
    }
    


  • Warum parst du die Zahlen beim Datei-Einlesen nicht gleich in ein exaktes Format?



  • Wer Dezimalzahlen ohne Rundungsfehler berechnen will, darf kein double verwenden.
    Entweder verwendet man http://gmplib.org/ oder man rechnet intern mit int's, long's oder long long's und fügt bei der Ausgabe einen '.' ein.

    Aber du hast zum Teil Recht, dass bei deiner Version weniger Rundungsfehler auftreten können.



  • Nexus schrieb:

    Warum parst du die Zahlen beim Datei-Einlesen nicht gleich in ein exaktes Format?

    Falls die Frage an mich geht:
    Die Zahlen sind als double gespeichert.
    Also nicht: 134.46, sondern via:

    fwrite(&doublezahl, sizeof(double), ...



  • Belli schrieb:

    Die Beträge haben zwei zu berücksichtigende Nachkommastellen und sind in der Datei als double-Werte gespeichert.
    ..
    mit
    fwrite(&doublezahl, sizeof(double), ...

    Vielleicht ist es jetzt einfach schon zu spät und ich verstehe dein Problem nicht richtig.
    Okay, war zu spät, gute Nacht 🙂



  • Wenn die Daten binär als double (nicht String) gespeichert sind, kannst du nicht bis zu "." einlesen.

    Aber die Frage ist dann halt: Warum als double speichern, wenn man sich der Rundungsproblematik bewusst ist? Kannst du das mit einem long nicht so einstellen, dass die untersten beiden Dezimalstellen die Nachkommastellen darstellen (:p)? Also quasi Cents als long speichern. Oder sonst auch zwei separate int s oder so.


Anmelden zum Antworten