Modulo falsch Berechnung?



  • @Gestalt sagte in Modulo falsch Berechnung?:

    double d=90000.025;
    double a=d/25*1000; //=3600001;

    Der Kommentar ist schon falsch:

    a ist nicht gleich 3600001!

    Lass dir einfach mal a - 3600001 ausgeben. Es kommt da -4.6566129e-10 raus.
    Das sollte auch unabhängig von 32/64 Bit sein, denn hier wirst du wohl in beiden Fällen mit IEEE754 doubles rechnen.

    Dein Grundfehler ist, dass du erwartest, dass Fließkommazahlen exakt sind. Es gibt zwei Möglichkeiten: erst gar nicht mit Fließkommazahlen arbeiten oder Runden. Du könntest also vor dem Modulo z.B. a = std::floor(a + 0.00001) rechnen. (wie viele Nullen ist abhängig davon, wie weit unter einem Wert du zulassen willst) Aber float-Arithmetik ist immer "shaky" 🙂



  • @DNKpp Hi, Du meinst warum ich jetzt AnsiString.ToDouble() mit eingebaut habe? Als ich das Programm damals geschrieben habe das ist wirklich schon sehr lange her, sozusagen eines meiner aller ersten Programme überhaupt, hatte ich nur die Möglichkeit in 32bit zu kompilieren was auch vollkommen ausreicht für dieses Programm. Es funktioniert auch wunderbar in 32bit was die Berechnung betrifft. Nun habe ich für das Programm weil ich eine andere Komponente eingebaut habe auf einen neueren Compiler umsteigen müssen. Nun hat der Compiler die Möglichkeit den Code als 64bit zu kompilieren. Das habe ich mal gemacht und dabei festgesellt das in 64bit der Modulo falsch berechnet wird. Als Lösung warum weiß ich nicht aber es funktioniert durch probieren bin ich dann auf die AnsiString.ToDouble() funktion gestoßen, wenn ich diese für 64bit mit einbaue rechnet er den Modulo korrekt aus. Aber bei 32bit brauche ich diese AnsiString Funktion nicht, der Modulo wird trotzdem richtig berechnet. Bisher habe ich keine andere Möglichkeit gefunden diese Rechenoperation in 64bit durchzuführen außer eben AnsiString.ToDouble() mit einzubauen. Das Problem tritt nur bei 64bit auf. Ich habe an dem ursprünglichen Code zur Berechnung also nix geändert außer wenn er nicht 32bit kompiliert diese AnsiString Funktion um die Code-Zeile herumgebaut.



  • @Gestalt sagte in Modulo falsch Berechnung?:

    @DNKpp Hi, Du meinst warum ich jetzt AnsiString.ToDouble() mit eingebaut habe? Als ich das Programm damals geschrieben habe das ist wirklich schon sehr lange her, sozusagen eines meiner aller ersten Programme überhaupt, hatte ich nur die Möglichkeit in 32bit zu kompilieren was auch vollkommen ausreicht für dieses Programm. Es funktioniert auch wunderbar in 32bit was die Berechnung betrifft. Nun habe ich für das Programm weil ich eine andere Komponente eingebaut habe auf einen neueren Compiler umsteigen müssen. Nun hat der Compiler die Möglichkeit den Code als 64bit zu kompilieren. Das habe ich mal gemacht und dabei festgesellt das in 64bit der Modulo falsch berechnet wird. Als Lösung warum weiß ich nicht aber es funktioniert durch probieren bin ich dann auf die AnsiString.ToDouble() funktion gestoßen, wenn ich diese für 64bit mit einbaue rechnet er den Modulo korrekt aus. Aber bei 32bit brauche ich diese AnsiString Funktion nicht, der Modulo wird trotzdem richtig berechnet. Bisher habe ich keine andere Möglichkeit gefunden diese Rechenoperation in 64bit durchzuführen außer eben AnsiString.ToDouble() mit einzubauen. Das Problem tritt nur bei 64bit auf. Ich habe an dem ursprünglichen Code zur Berechnung also nix geändert außer wenn er nicht 32bit kompiliert diese AnsiString Funktion um die Code-Zeile herumgebaut.

    Ich würde jetzt mal schätzen, dass dieser AnsiString CTor dein double sliced und eben nicht 1:1 in einen String konvertiert. Und ToDouble liefert dann eben einen leicht anderen Wert zurück, mit dem deine Operationen dann scheinbar besser zurecht kommen. Ich würde jedenfalls nicht solche seltsamen Wege vertrauen, sondern ggf durch debuggen die unterschiede mal analysieren und dann explizit umarbeiten. Die Anderen haben dir ja auch noch ein paar Hinweise gegeben.



  • @DNKpp bin zumindest erstmal dabei die Lösungsansätze durchzuprobieren



  • @DNKpp fmod scheint es nicht zu sein



  • Du solltest überhaupt nicht bei Zeiten (hour, min, sec, msec) mit Fließkommazahlen arbeiten (dies ist genauso ungenau wie z.B. für Geldwerte).

    Kannst du nicht die chrono-Bibliothek benutzen?



  • @wob ja wie Du sagst es ist tatsächlich so, ich bin davon ausgegangen.

    So habe es mal durchprobiert mit verschiedenen Werten 32bit/64bit

        double d=90000.024;
        double a=d/25*1000;
        a = std::floor(a + 0.001);
        int r = static_cast<int>(a)%1000;   //richtig 0
    
        cout << r << endl;
    
        d=90000.025;
        a=d/25*1000;
        a = std::floor(a + 0.001);
        r = static_cast<int>(a)%1000;   //richtig 1
    
        cout << r << endl;
    
        d=90000.049;
        a=d/25*1000;
        a = std::floor(a + 0.001);
        r = static_cast<int>(a)%1000;   //richtig 1
    
        cout << r << endl;
    
        d=90000.050;
        a=d/25*1000;
        a = std::floor(a + 0.001);
        r = static_cast<int>(a)%1000; //richtig 2
    
        cout << r << endl;
    
        d=90000.074;
        a=d/25*1000;
        a = std::floor(a + 0.001);
        r = static_cast<int>(a)%1000; //richtig 2
    
        cout << r << endl;
    
        d=90000.075;
        a=d/25*1000;
        a = std::floor(a + 0.001);
        r = static_cast<int>(a)%1000; //richtig 3
    
        cout << r << endl;
    

    std::floor(a + 0.00001) damit geht es auch, brauch ich sowieso für 5 Stellig hinter Komma bei nicht PAL



  • @Gestalt

    Willkommen in der Welt der Gleitkomma-Zahlen! Dein Problem ist dass du im Endeffekt ein double nach int casten und hierbei alle Nachkommastellen abschneiden möchtest.

    Im Testfall 90000.025 ist der Wert d/25*1000 bei mir jedoch 3600000.9999999995, sollte aber 3600001.0 sein.

    Also habe ich den cast etwas verändert. Ich habe die Zahl auf die 5. Nachkommastelle gerundet und dann alle Nachkommastellen abgeschnitten.

    #include <cstdio>
    #include <cmath>
    #include <cassert>
    
    
    int Foo(double d)
    {
    	static constexpr unsigned int n = 5;
    	double Factor = std::pow(10.0, 5);
    	double a = d / 25 * 1000;
    	//int ai = (int)(a);
    	int ai = static_cast<int>(std::floor(a * Factor + 0.5) / Factor);
    	int e = ai % 1000;
    	return e;
    }
    
    
    int main()
    {
    	int e;
    	
    	e = Foo(90000.049);
    	assert(e == 1);
    
    	e = Foo(90000.050);
    	assert(e == 2);
    
    	e = Foo(90000.025);
    	assert(e == 1);
    	
    	e = Foo(90000.024);
    	assert(e == 0);
    
    	return 0;
    }
    

    BTW: Der Code würde ich in eine Testreihe packen.



  • @wob So hab das jetzt mal für NTSC mal durch 32/64bit, geht super:

        //NTSC
        d=7191.04794;
        a=d/23.97*1000;
        a = std::floor(a + 0.00001);
        r = static_cast<int>(a)%1000; //richtig 2
    
        cout << r << endl;
    
        //NTSC
        d=7191.04793;
        a=d/23.97*1000;
        a = std::floor(a + 0.00001);
        r = static_cast<int>(a)%1000; //richtig 1
    
        cout << r << endl;
    

    Danke Dir 🙂 und an alle anderen natürlich auch. Ach was ich noch vergessen habe die ganze Zeit zu erwähnen die Ergebnisse spiegeln lediglich die millisekunden wieder, falls welche vorhanden sind.



  • @Quiche-Lorraine sagte in Modulo falsch Berechnung?:

    #include <cassert>

    so hab das mal durchprobiert für NTSC , scheint auch damit zu funktionieren , ich denke aber das ich den Code std::floor(a + 0.00001); nehmen werde, sieht einfacher aus. 🙂



  • Ich habe mir auch Deinen Code angeschaut. Das Hauptproblem in Deinem Code ist, dass Du nicht verstehst, dass sich binäre Zahlen nicht immer auf dezimale Zahlen und umgekehrt abbilden lassen. Ich habe dazu eine Routine geschrieben, die das Binäre IEEE754 Double Precision Format Binär dekodiert und ausgibt. (Negative Exponenten werden noch nicht dekodiert sondern direkt ausgegeben.) Daran kannst Du besser erkennen, weshalb Du hier Probleme hast.

    #include <iostream>
    #include <iomanip>
    #include <cmath>
    #include <bitset>
    #include <string>
    
    using std::setprecision, std::setw, std::fixed;
    
    constexpr int w = 10;
    constexpr int p = 5;
    
    using DT = double;
    using IT = int;
    
    void print_double(const double d) {
    	const char* const info[5] = {" denormalized", "   normalized", " infinity", " NaN", " denormalized zero"};
    	const char* const lead[2] = {"1.", "0."};
    	char sign[2] = {'+', '-'};
    	uint64_t* u64 = (uint64_t*)&d;
    	uint64_t mantissa = 0x000FFFFFFFFFFFFF & *u64;
    	uint64_t exponent = 0x7FF0000000000000 & *u64;
    	uint64_t sign64   = 0x8000000000000000 & *u64;
    	sign64 = sign64 >> 63;
    	exponent = exponent >> 52;
    	size_t index = 0, lead_index = 0;
    
    	if ((0 == exponent) && (0 == mantissa)) {
    		index = 4;
    		lead_index = 1;
    	} else if ((0 == exponent) && (0 < mantissa)) {
    		index = 0;
    		lead_index = 1;
    		exponent = 1 - exponent;
    	} else if ((0 < exponent) && (2047 > exponent)) {
    		index = 1;
    		lead_index = 0;
    		exponent -= 1023;
    	} else if ((2047 == exponent) && (0 == mantissa)) {
    		index = 2;
    	} else if ((2047 == exponent) && (0 < mantissa)) {
    		index = 3;
    	}
    
    	std::bitset<52> bmantissa(mantissa);
    	std::bitset<11> bexponent(exponent);
    
    	std::cout << setw(w) << setprecision(p) << d << ", " << sign[sign64] << lead[lead_index] << bmantissa << " ×2^ " << bexponent << info[index] << "\n";
    }
    
    
    template<typename T1 = DT, typename T2 = IT>
    void calculate_and_print (const T1 d, const T2 correct_result) {
    	T1 a = (d/25) * 1000;
    	T2 i = round(a);
    	T2 e = i%1000;
    
    	uint64_t* pointer = (uint64_t*)&a;
    	uint64_t mantissa = 0x000FFFFF & (*pointer);
    	uint64_t exponent = 0x7FF00000 & (*pointer);
    
    	exponent = exponent >> 52;
    
    	std::cout << fixed;
    	std::cout << correct_result << ", " << e << ", " << i << ", " << setw(w) << setprecision(p) << d << "\n";
    	print_double(a);
    	std::cout << "\n";
    }
    
    int main() {
    	std::cout << "test output of some numbers\n";
    	print_double( 0.0);
    	print_double( 0.125);
    	print_double( 1.0);
    	std::cout << "\n";
    
    	calculate_and_print(90000.049, 1);
    	calculate_and_print(90000.050, 2);
    	calculate_and_print(90000.025, 1);
    	calculate_and_print(90000.024, 0);
    }
    


  • @Gestalt sagte in Modulo falsch Berechnung?:

    7191.04794

    Ich check grad nicht was du erreichen willst. Willst du ausrechnen wie lange 7191.04794 Frames eines NTSC Videos brauchen? Und von der Zeit dann sozusagen den Nachkommaanteil in Millisekunden? Verstehe nicht wozu das gut sein soll.



  • Um die Probleme zu umgehen geht auch folgendes, einfach die Werte bisschen erhöhen dann klappts auch ohne Werte dazu addieren, runden oder sonstiges.

    	double A = 90000.024*10;
    	double B = 0.025*10;
    	double C = (A/B);
    	int R = static_cast<int>(C)%1000; //0
    
    	cout << R <<endl;
    
    	A = 90000.025*10;
    	B = 0.025*10;
    	C = (A/B);  //3600001
    	R = static_cast<int>(C)%1000;   //1
    
    	cout << R <<endl;
    
    	if(C==3600001)
    		cout << "true"  <<endl;
    

    is schon spät muss erstmal pennen

    Update: ist Schrott funktioniert nicht zuverlässig.



  • @Gestalt sagte in Modulo falsch Berechnung?:

    double B = 0.025*10;

    Naja, 0.25 kann exakt in double oder float dargestellt werden, 0.025 aber nicht. 0.24 übrigens nicht. Deine Berechnung funktioniert also nur "zufällig", du solltest bei Fließkommazahlen IMMER mit Rundungsfehlern/Darstellungsproblemen rechnen.

    Dein erster Fall funktioniert, weil 3600000.96 (bzw. ...959999... in double) rauskommt, was sowieso in der 2. Nachkommastelle klein ist, d.h. 0.96 ist weit genug von der 1 entfernt, sodass du kein Problem hast.

    Der 2. Fall ist spannender. Er funktioniert, weil 0.25 exakt ist.

    Mit nicht exakt darstellbaren Nachkommazahlen, die mathematisch gesehen aber in deiner Rechnung eine ganze Zahl ergeben, wirst du Probleme haben, weil das Ergebnis dann auch geringfügig größer (kein Problem für dich) oder kleiner (da hast du dann ein Problem) als die ganze Zahl sein kann.



  • @wob sagte in Modulo falsch Berechnung?:

    stellungsproblemen rec

    habe zur Not noch Deine Variante die ja noch im Code steht, ich probiere das aber später nochmal durch bis es kracht :). Nur im Moment bastel ich an anderen Dingen, die mich 4 Stunden ehr ausm Bett geholt haben als eigentlich geplant.

    Update: es bleibt dabei "std::floor(a + 0.00001);" beste Variante.


  • Gesperrt

    Tipp: Wenn es float, double, usw. sein soll ... und die Ergebnisse genau sein sollen ... dann immer die C++-Boost-Library verwenden. Die Jungs machen quasi nix anderes.

    Und hier https://en.wikipedia.org/wiki/Accuracy_and_precision und hier https://en.wikipedia.org/wiki/Propagation_of_uncertainty steht mehr darüber.

    Edit: Eigentlich wollte ich das in die Signatur schreiben, aber das darf ich nicht:

    Menschen machen Fehler, aber Computer machen noch mehr Fehler. 😉



  • @hustbaer sagte in Modulo falsch Berechnung?:

    @Gestalt sagte in Modulo falsch Berechnung?:

    7191.04794

    Ich check grad nicht was du erreichen willst. Willst du ausrechnen wie lange 7191.04794 Frames eines NTSC Videos brauchen? Und von der Zeit dann sozusagen den Nachkommaanteil in Millisekunden? Verstehe nicht wozu das gut sein soll.

    Das würde mich auch interessieren. Ich habe das Gefühl, dass man hier möglicherweise schon früher einen anderen Ansatz wählen kann, um Rundungsfehler zu minimieren. Soweit ich weiß sind diese PAL/NTSC Frameraten und Frequenzen auch alles Rationale Zahlen, da könnte man sogar weitgehend auf Fließkomma-Berechnungen verzichten, wenn man es wirklich exakt haben will - bzw. erst ganz am Ende das Ergebnis nach double umrechnen. So akkumulieren sich die Fehler in den Rechenschritten dann auch nicht auf.

    NTSC macht so weit ich weiß 300001001=29.970029{30000 \over 1001} = 29.\overline{970029} Bilder pro Sekunde, bzw. 100130000=0.03336{1001 \over 30000} = 0.0333\overline{6} Sekunden pro Bild. Für 7191.04794 Frames (falls das hier überhaupt berechnet werden soll (?)) wären das dann 719104794100000100130000=7198238987943000000000=239.941299598{719104794 \over 100000} \cdot {1001 \over 30000} = {719823898794 \over 3000000000} = 239.941299598 Sekunden (exakt!).

    Das nur als Beispiel, wie man Berechnungen wie diese mit Integer-Arithmetik in Zähler und Nenner machen und erst ganz am Schluss nach Fließkomma konvertieren könnte. Ich würde vermuten, dass sich damit einige Rundungs-Scherereien vermeiden ließen. Wäre trotzdem gut zu wissen, was du überhaupt berechnen willst @Gestalt - vielleicht ist das ja ein XY-Problem (?).



  • @Finnegan sagte in Modulo falsch Berechnung?:

    7191.04794

    ja für mich ist es sinnvoll sonst würde ich es nicht machen und Zeit in ein Programm stecken. In Deiner Berechnung nutzt Du das falsche Format für den Wert "7191.04794", diesem Wert liegt NTSC-FILM 23,97fps zugrunde und nicht 29,97fps. Diese "7191.04794" sind genau und zwar exakt nocht exakter geht es nicht 05 Stunden 00 Minuten 00 sekunden und 002 Millisekunden. Mein Programm funktioniert ich habe auch keine Rundungsfehler auch nicht bei 999 Stunden 59 Minuten 59 Sekunden 999Millisekunden das sind Exakt und ganz genau 86291999.97603 Frames im 23,97 NTSC FILM Format. Ich finde das nicht gut das man nach dem Sinn gefragt wird nur weil ich Dinge mache die eine anderer nicht macht und er deshalb darin keinen Sinn sieht.



  • Trotzdem benutzt man dafür dann aber keine Fließkommazahlen!



  • @Th69 Ok, dann Beweise mir das in dem Du "5006398.17" im NTSC 23,97 umrechnest zu Stunden/Minuten/Sekunden/Millisekunden ohne Fließkomma-Datentypen zu benutzen. Das Ergebniss reicht vorerst aus.


Anmelden zum Antworten