Genauigkeitsproblem



  • Hallo,
    ich habe leider ein kleines Genauigkeitsproblem:

    Ich habe verschiedene Gebäude, die pro Frame einmal angesprochen werden, um die Abrechnungen zu machen. Als Parameter wird dieser Funktion dann die verstrichende Zeit seit dem letzten Frame mitgegeben:

    BOOL CArbeiterGebeude::Move(float fTime)
    {
    	long double fTime2 = (long double) fTime;
    	char acText2[512];
    	static std::ofstream schreiben("C:\\DEBUG2.txt");
    	if(g_pSpiel->m_pGame->m_babrechnen)
    	{
    
    		sprintf_s(acText2, "\nZeit1: %.5f\nZeit2: %.5f\nVORHER:\n\nEinnahmen: %.5f  \nAusgaben: %.5f   \nBilanz1: %.5f   \nGeld: %.5f   \nGeld_old %.5f   \nBilanz2:  %.5f ", fTime, fTime2, g_pSpiel->m_pGame->m_dEinnahmen, g_pSpiel->m_pGame->m_dAusgaben, g_pSpiel->m_pGame->m_dEinnahmen-g_pSpiel->m_pGame->m_dAusgaben, g_pSpiel->m_pGame->m_dGeld, g_pSpiel->m_pGame->m_dGeldold, g_pSpiel->m_pGame->m_dGeld-g_pSpiel->m_pGame->m_dGeldold);
    		schreiben<<acText2;
    	}
    
    	long double WertT =m_sTyp->Kosten_Sanierung*fTime2/10.0;
    	g_pSpiel->m_pGame->m_dGeld -= WertT;
    	g_pSpiel->m_pGame->m_dAusgaben += WertT;
    
    	if(g_pSpiel->m_pGame->m_babrechnen)
    	{
    		sprintf_s(acText2, "\n\nNACHHER:\n\nEinnahmen: %.5f  \nAusgaben: %.5f   \nBilanz1: %.5f   \nGeld: %.5f   \nGeld_old %.5f   \nBilanz2:  %.5f ", g_pSpiel->m_pGame->m_dEinnahmen, g_pSpiel->m_pGame->m_dAusgaben, g_pSpiel->m_pGame->m_dEinnahmen-g_pSpiel->m_pGame->m_dAusgaben,g_pSpiel->m_pGame->m_dGeld, g_pSpiel->m_pGame->m_dGeldold, g_pSpiel->m_pGame->m_dGeld-g_pSpiel->m_pGame->m_dGeldold);
    		schreiben<<acText2;
    	}
    

    Wenn am Ende dann die Differenz zwischen m_dAusgaben und m_dEinnahmen für die Bilanz berechnet wird, gibt es leider eine Spanne zwischen der wirkliche Differenz zwischen dem ursprünglichen Geldbetrag und dem jetzigen Geldbetrag.
    Wenn die Differenz nicht größer als eins oder zwei wäre, dann fänd ich das nicht so schlimm. Aber diese Abweichung hat pro 10 Sekunden schon Werte um die 30 erreicht.
    Deshalb hab ich vor und nach der Rechnung diese beiden Blöcke eingefügt, die die aktuellen Daten ausgeben sollen, damit man einen besseren Überblick über die Abrechnung bekommt:

    Zeit1: 0.05801
    Zeit2: 0.05801
    VORHER:

    Einnahmen: 0.00000
    Ausgaben: 0.00000
    Bilanz1: 0.00000
    Geld: 504634.18750
    Geld_old 504634.18750
    Bilanz2: 0.00000

    NACHHER:

    Einnahmen: 0.00000
    Ausgaben: 0.17403
    Bilanz1: -0.17403
    Geld: 504634.00000
    Geld_old 504634.18750
    Bilanz2: -0.18750

    Dies ist die Ausgabe einer Abbrechnung, bei der Kosten_Sanierung 30 betrug.
    Leider kann man hier an dieser einen Abrechnung schon erkennen, dass bereits nach nur einer Abbrechnung die Bilanzen nicht mehr zueinander passen.
    Kann mir einer vielleicht erklären, woran das liegen könnte, ich hab extra schon "long double" als Datentyp genommen, um eben große Genauigkeit zu haben.

    In einem Vergleichsprojekt, in dem die ausgegebenen Daten direkt eingetragen worden sind tritt der Fehler aber leider nicht auf:

    long double m_dGeld = 504634.18750;
    	long double m_dGeldold = 504634.18750;
    	long double m_dAusgaben = 0.0;
    	long double m_dEinnahmen = 0.0;
    
    	printf("\nVORHER:\n\nEinnahmen: %.5f  \nAusgaben: %.5f   \nBilanz1: %.5f   \nGeld: %.5f   \nGeld_old %.5f   \nBilanz2:  %.5f ", m_dEinnahmen, m_dAusgaben, m_dEinnahmen-m_dAusgaben, m_dGeld, m_dGeldold, m_dGeld-m_dGeldold);
    	int Kosten_Sanierung=30;
    	float fTime = 0.05801f;
    
    	long double WertT =Kosten_Sanierung*fTime/10.0;
    	m_dGeld -= WertT;
    	m_dAusgaben += WertT;
    
    	printf("\n\nNACHHER:\n\nEinnahmen: %.5f  \nAusgaben: %.5f   \nBilanz1: %.5f   \nGeld: %.5f   \nGeld_old %.5f   \nBilanz2:  %.5f ", m_dEinnahmen, m_dAusgaben, m_dEinnahmen-m_dAusgaben,m_dGeld, m_dGeldold, m_dGeld-m_dGeldold);
    

    Bei diesem Code passend die Bilanzen zueinander.
    Hat jemand eine Idee, worin das Problem liegt, und wie ich es beheben kann?
    Muss man einen noch genaueren Datentyp als "long double" nehmen? Aber warum tritt das Problem dann nicht bei dem Projekt auf, indem die Daten direkt so hereingeschrieben worden sind?
    (ich verwende VC 2005)

    Vielen Dank für Hilfe, viele Grüße
    Andreas



  • Hi, welchen Datentyp haben denn die Member der Struktur 'g_pSpiel->m_pGame' (ok, sehe gerade, daß du sie ja mit "%.5f" ausgibst, also sind es nur float-Werte)?

    Am besten du nimmst als Standarddatentypen 'double' (long double benötigst du nur bei sehr großen Werten bzw. sehr großer Genauigkeit).
    Es bringt bei dir ja nichts, wenn du in deiner Funktion lokal zwar mit long double rechnest, aber nur float-Werte dann abspeicherst (und beim nächsten Durchlauf dann damit weiterrechnest).

    Müßtest du nicht eigentlich mit "%.5Lf" long double Werte ausgeben???
    (alternativ kannst du den Streaming Operator << dafür in C++ benutzen!)

    P.S. Noch als Tipp: ich würde dir raten einen lokalen Zeiger (bzw. besser noch eine Referenz) auf 'g_pSpiel->m_pGame' anzulegen, damit man das besser lesen kann!



  • Hi, erstmal vielen Dank für die Antwort.

    Tut mir leid das ich das nicht dabei geschrieben habe, aber wenn du bei den Membervariablen von m_pGame m_dGeld und so meinst dann sind das auch long double Werte. (Das untere Beispiel sollte eine Originalgetreue Nachbildung von dem Code sein, bei dem der Fehler auftritt, nur wurden hier die Anfangszahlen manuell nach den ausgegebenen Werten festgelegt)

    Bei der Ausgabe hab ich einfach .5f genommen, da es bisher auch bei double-Werten funktioniert hatte.
    Ich glaub aber nicht, dass die fehlerhaften Zahlen an der Ausgabe liegen, ich hab mir im Debugger jeden Schritt anzeigen lassen und die Variablen überwacht, wobei hierbei auch der gleiche Fehler aufgetreten ist.

    Kann es sonst noch einen Grund geben der zu dieser Ungenauigkeit führt?

    Viele Grüße
    Andreas



  • Das Problem ist, dass double (auch long double) immer fehlerbehaftet ist, sobald es nicht mehr um zweierpotenzen geht. Stell dir ein System mit Fließkommazahlen im Zehnersystem vor, in dem wild mit siebteln, dritteln und anderen eigentlich periodischen Zahlen herumgerechnet wird. So ein System hat eine entdliche Genauigkeit, schneidet also irgendwo die Mantissen ab und bekommt Rundungsfehler. Das Selbe passiert bei double & Co, nur dass hier im zweiersystem gerechnet wird. Gerade wenn du schon so große Zahlen vor dem Komma hast, nimmt die Genauigkeit hinter dem Komma ab.
    Da es sich bei Geld immer um eine Größe mit fester Nachkommazahl handelt, ist es am sinnvollsten, intern mit Integerzahlen zu rechnen und bei der Ausgabe dann mit einem entsprechend eingestreuten Komma zu versehen.



  • Also ich tippe immer noch darauf, daß irgendwo nur mit floats gerechnet (bzw. abgespeichert) wird, da die anderen Datentypen eine wesentlich größere Präzision haben:
    float: ca. 6-7
    double: ca. 15
    long double: ca. 34

    Aber für Geldwerte solltest du (so wie pumuckl schon geschrieben hat), am besten mit Integern (int, long, long long) arbeiten, um mögliche Rundungsfehler zu vermeiden.

    P.S: Wo (bzw. wann) setzt du eigentlich 'g_pSpiel->m_pGame->m_dGeldold'?



  • Hallo, erstmal vielen Dank für die Antworten!!!

    Die Variablen wurden so deklariert in Game.h:

    long double					m_dGeld;
    	long double					m_dAusgaben;			// Ausgaben für einen Monat
    	long double					m_dEinnahmen;			// Einnahmen für einen Monat
    
    	long double					m_daAusgaben;			// Ausgaben für einen Monat
    	long double					m_daEinnahmen;			// Einnahmen für einen Monat
    
    	long double					m_dGeldold;
    	double					m_dBilanz2;
    

    m_dGeldold wird folgendermaßen gesetzt:

    if(m_fTime > DauerMonat) 
    	{	
    		//schreiben<<"NEUNER MONAT\n\n";
    		machAbrechnung(fTime-(m_fTime-DauerMonat));
    		//machAbrechnung(10.0f);
    		m_iMonat++;
    		m_bNeuer_Monat = TRUE;
    		m_iHauslvlUP = 0;
    		m_dBilanz = m_dEinnahmen-m_dAusgaben;
    		m_dBilanz2 = m_dGeld-m_dGeldold;
    		m_dGeldold = m_dGeld;
    		m_daEinnahmen = m_dEinnahmen;
    		m_dEinnahmen = 0.0;
    		m_daAusgaben = m_dAusgaben;
    		m_dAusgaben = 0.0;
    ....
    }
    

    Die gezeigte Abrechnung von den vorherigen Zahlen kommt direkt nach diesem Code, da ja m_dAusgaben und Co noch auf Null standen.

    Ich hab den Code nochmal debugged und mir die Zahlen genau angeschaut:

    long double WertT =m_sTyp->Kosten_Sanierung*fTime2/10.0; // 1. Zeile
    g_pSpiel->m_pGame->m_dGeld -= WertT; // Zweite Zeile
    g_pSpiel->m_pGame->m_dAusgaben += WertT; //Dritte Zeile
    

    Ich hab jeweils nach dem abarbeiten einer Zeile mir die Variablen ausgeben lassen:

    vor der ersten Zeile:
    		WertT							-9.2559631349317831e+061	double
    		fTime2							0.040189463645219803		double
    		fTime							0.040189464				float
    		g_pSpiel->m_pGame->m_dGeld		504610.56250000000		double
    		g_pSpiel->m_pGame->m_dAusgaben	0.00000000000000000		double
    		g_pSpiel->m_pGame->m_dGeldold		504610.56250000000		double
    		m_sTyp->Kosten_Sanierung		30					int
    
    Nach der ersten Zeile:	
    		WertT							0.12056839466094971		double
    		fTime2							0.040189463645219803		double
    		fTime							0.040189464				float
    		g_pSpiel->m_pGame->m_dGeld		504610.56250000000		double
    		g_pSpiel->m_pGame->m_dAusgaben	0.00000000000000000		double
    		g_pSpiel->m_pGame->m_dGeldold		504610.56250000000		double
    		m_sTyp->Kosten_Sanierung		30					int
    
    Nach der zweiten Zeile:
    		WertT							0.12056839466094971		double
    		fTime2							0.040189463645219803		double
    		fTime							0.040189464				float
    		g_pSpiel->m_pGame->m_dGeld		504610.43750000000		double
    		g_pSpiel->m_pGame->m_dAusgaben	0.00000000000000000		double
    		g_pSpiel->m_pGame->m_dGeldold		504610.56250000000		double
    		m_sTyp->Kosten_Sanierung		30					int
    
    Nach der dritten Zeile:
    		WertT							0.12056839466094971		double
    		fTime2							0.040189463645219803		double
    		fTime							0.040189464				float
    		g_pSpiel->m_pGame->m_dGeld		504610.43750000000		double
    		g_pSpiel->m_pGame->m_dAusgaben	0.12056839466094971		double
    		g_pSpiel->m_pGame->m_dGeldold		504610.56250000000		double
    		m_sTyp->Kosten_Sanierung		30					int
    

    Mir sind da jetzt ein paar Dinge beim Nachrechnen mit dem Taschenrechner aufgefallen:

    1. WertT hat der Computer so berechnet: 0.12056839466094971
      nach meinem Taschenrechner: 0.1205683909
      => Macht eine Abweichung ab der 8. Nachkommastelle

    2. Der Debugger hat hinter die Variablenangabe jeweils den Typ geschrieben, wobei dort aber immer nur double und nie long double stand, kann das sein, dass VC 2005 long double nicht umgesetzt hat, oder ist das nur eine verkürtzte Debuggerausgabe?

    3. Nach dem abarbeiten der zweiten Zeile, habe ich mal mit dem Taschenrechner die Differenz zwischen Geld und Geldold berechnet, wobei dabei mehrfach eine Differenz von 0,125 zustande kam, obwohl man eigentlich eine Differenz von 0.1205...(WertT) erwarten würde. Das mit 0,125 würde wieder für das Problem mit den zweier Potenzen sprechen, da 0,125 ja 2^-3 ist.

    4. Der Debugger gibt die Variablen auf sehr viele Nachkommastellen aus, bsp: 504610.43750000000, wobei allerdings sehr viele dabei Null bleiben, welches eigentlich nicht sein dürfte.

    In dem Erstatzprojekt, in dem die Berechnung klappte sind diese vielen Nullen nicht vorhanden.
    Ich hab auch mal folgendes noch in das Fehlerprojekt geschrieben:

    long double dummy6 = 500000.7328123456798187;
    dummy6 -= 		0.11994317919015884;
    

    Wobei er erst alle Nullen mit den Zahlen ausgefüllt hatte, nach der Subtraktion allerdings wieder nur auf die vierte Nachkommastelle genau war.

    Ich kann mir dieses komische Verhalten nicht erklären, liegt das daran, dass es unterschiedliche Projekteinstellungen gibt, die den Compiler veranlassen stark zu optimieren? (Das Fehlerprojekt ist eine Win 32-Anwendung mit GUI, das andere eine Konsolen-Anwendung)
    Ich bin ehrlich gesagt, ziemlich ratlos, was man dagegen machen kann, wisst ihr vielleicht etwas?
    (In beiden Projekten steht zumindest "Gleitkommamodell" auf "Precise (/fp:precise)".
    Viele Grüße
    Andreas



  • Hallo, erstmal vielen Dank für die ganzen Antworten,
    ich hab den Fehler jetzt endlich gefunden 🙂

    DirectX hat mir die Genauigkeit des FPU herabgesetzt, ich hätte das Flag D3DCREATE_FPU_PRESERVE mitangeben müssen, damit dieses nicht passiert.

    Viele Grüße
    Andreas


Anmelden zum Antworten