Wie funktioniert Konvertierung von double zu int?



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



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

    Sie sind in keinster Weise "genau". Integer haben exakte Werte und solange man im Wertebereich bleibt, wird auch jede Addition ein exaktes Ergebnis liefern. Letzteres ist für Fließkommazahlen eben schon nicht gegeben. Auch ein grund dafür, warum man wenn man viele Fließkommazahlen addieren will, sie vorher sortieren sollte und beim addieren mit den Kleinsten beginnt, so das die Rechenverluste möglichst gering sind.



  • Michael E. schrieb:

    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.

    Wer soll das verstehen mit deiner Float-Arithmetik für integer und dem Gebrauch von Offsets? 😕 Ich nicht!!! 😞 Wozu kennen denn alle Compiler seit Olims Zeiten verschiedene Datentypen wie integer und float???

    Hat jedoch wenig mit Programmierung oder C++ zu tun, sondern eher mit dem Grundlagenwissen der Mathematik. Lernt man nach dem Kindergarten in der Schule!
    Ich programmiere grundsätzlich zuerst im Kopf und wähle danach die geeigneten Methoden aus. Hat mir bisher nicht geschadet. Offsets kannte ich nur aus 8- oder 16-bit-Zeiten die für Adressierung von Speicherbereichen 😡

    @asc: Ja, Integers sind exakt auf eben ganzzahlige Werte. Alles dazwischen geht schlicht verloren, was möglicherweise fatale Wirkung haben kann. Da soll mal jemand bei einer Bank die abgeschnittenen Zinserträge der Kunden zu seinem Gunsten aufaddiert haben - kam richtig was raus - ist aber aufgefallen!



  • berniebutt schrieb:

    Wer soll das verstehen mit deiner Float-Arithmetik für integer und dem Gebrauch von Offsets? 😕 Ich nicht!!! 😞

    Das hast du doch selbst vorgeschlagen:

    Alternativen:
    1. Mit longint in Cent rechnen

    Wozu kennen denn alle Compiler seit Olims Zeiten verschiedene Datentypen wie integer und float???

    Weil es auch Anwendungsbereiche für Fließkommazahlen gibt. Beim Rechnen mit Währungen nimmt man aber besser Festkommazahlen. Diese simuliert man in C++ eben am einfachsten, indem man sich immer nen Exponenten denkt (-> Cents statt Euro).

    Hat jedoch wenig mit Programmierung oder C++ zu tun, sondern eher mit dem Grundlagenwissen der Mathematik. Lernt man nach dem Kindergarten in der Schule!

    Was hat wenig mit Programmierung zu tun und was lernt man direkt in der Schule? Deine Beiträge enthalten leider oft zusammenhangslose Sätze, mit denen ich nichts anfangen kann.

    Offsets kannte ich nur aus 8- oder 16-bit-Zeiten die für Adressierung von Speicherbereichen 😡

    Kein Grund böse zu werden.



  • berniebutt schrieb:

    @asc: Ja, Integers sind exakt auf eben ganzzahlige Werte. Alles dazwischen geht schlicht verloren, was möglicherweise fatale Wirkung haben kann.

    Wie exakt sind denn deiner Meinung nach Fließkommazahlen?

    Alles dazwischen geht schlicht verloren

    Bei Fließkommazahlen auch. Ich wüsste auch nicht, wie man es sonst anstellen sollte 😉

    Edit:

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

    Rechne mal

    float(int_max)-1
    

    wenn Float und Int gleich viel Speicher belegen.



  • @An Alle: Mannomann - wenn wir Oldie-Programmierer uns mit den seinerzeit schlechteren Compilern um solchen Kleinkram gekümmert hätten, wäre kein Programm gelaufen. Was macht ihr da heute? 😕

    Ein Programm muss zuverlässig laufen. Möglichst mit dem geringsten Aufwand zur Programmierung, nicht viel Laufzeit und Speicher verbraten - und sonst nichts! :p Bin ich hier mit meiner Meinung allein?

    @Fragesteller: Du bist auf dem richtigen Weg.


Anmelden zum Antworten