Wie funktioniert Konvertierung von double zu int?



  • 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.



  • Ja klar. Wenn man (ich) von Anfang an um die Problematik gewußt hätte, hätte man die Werte von vornherein als int - Werte bearbeitet und nur für die Ausgabe/Anzeige ein Komma an die richtige Stelle gesetzt.
    Aber ich habe hier eine Anwendung, die fast 20 Jahre alt ist; von den Rundungsproblemen wurde ich irgendwann viel später überrascht, wahrscheinlich ist das ein Problem, mit dem Programmieranfänger überhaupt nicht rechnen ...



  • Hallo Belli,

    willkommen im Club!

    Fliesskommazahlen (float, double, ...) sind nun einmal viel genauer als Ganzzahlen (int, ...). Man setzt sie deshalb auch vor allem in technischen und wissenschaftlichen Berechnungen ein. Bei Geldbeträgen - wie du gesehen hast - kann es unschöne Unstimmigkeiten geben. Vielleicht musst du mit der übernommenen Datei und den vorhandenen double-Werten keinen grossen Aufwand treiben. Dazu solltest du aber mehr über das die Datei auswertende Programm mitteilen.

    Du hast die Problematik erkannt und bist damit über den Anfänger hinaus gekommen. Der Rest ist 'Softwarepflege' versus 'Neuprogrammierung'. Ein 20 Jahres alte fremdes Programm anpassen zu wollen, ist nicht unbedingt ratsam und muss deswegen im Aufwand sehr sorgfältig durchdacht sein.



  • berniebutt schrieb:

    Hallo Belli,
    Fliesskommazahlen (float, double, ...) sind nun einmal viel genauer als Ganzzahlen (int, ...)...

    Ähmm nein. Fliesskommazahlen sind eben nicht genauer.



  • Fliesskommazahlen (float, double, ...) sind nun einmal viel genauer als Ganzzahlen (int, ...).

    nö. ganzzahlen sind genau(natürlich nur in ihrem wertebereich). fließkommazahlen sind die, die ungenau sind...



  • unskilled schrieb:

    nö. ganzzahlen sind genau(natürlich nur in ihrem wertebereich). fließkommazahlen sind die, die ungenau sind...

    Und eben in dem Wertebereich von Ganzzahlen - also z.B. zwischen 1 und 2 - sind Fliesskommazahlen sehr genau, sonst bräuchte man sie nicht!

    Beispiel:
    - ganzahlige Division 3 / 2 liefert 1
    - Flieskommadivision 3. / 2. liefert 1.5

    Was ist nun genau und was ungenau?

    Aber das lernt man schon im Kindergarten mit der einfachen Aufgabe 3 Äpfel gerecht auf 2 Kinder aufzuteilen. Man braucht dafür ein Messer zum Teilen und trifft es damit nie präzise genau, aber immerhin besser als jedem Kind 1 Apfel zu geben und den 3. Apfel selbst zu nehmen. :p



  • berniebutt schrieb:

    unskilled schrieb:

    nö. ganzzahlen sind genau(natürlich nur in ihrem wertebereich). fließkommazahlen sind die, die ungenau sind...

    Und eben in dem Wertebereich von Ganzzahlen - also z.B. zwischen 1 und 2 - sind Fliesskommazahlen sehr genau, sonst bräuchte man sie nicht!

    Blödsinn. Wenn alle Zahlen zwischen 1 und 2 sind, merkt man sich intern vielleicht nen Offset von 1 und den betragsmäßig größten Exponenten, sodass der Wertebereich der Integer verkleinert durch den Exponenten das Intervall noch ganz überdeckt, wenn man denn die höchste Genauigkeit mit einfachen Mitteln haben möchte.


Anmelden zum Antworten