Modulo falsch Berechnung?



  • Hallo,
    hab mal wieder ein Problem und zwar habe ich schon länger ein Programm am laufen was ich mir mal als 32bit kompiliert habe und das auch funktioniert, ist ein uraltes Ding welches ich seit paar Wochen schon umbaue um es mit einem aktuellen Compiler kompilieren zu können, was auch funktioniert. Nun habe ich das Ding aus versehen (weil ich es nicht als 64bit benötige) mal als 64bit kompiliert und erhalte falsche Werte in der Modulo Berechnung soweit habe ich das jetzt ausfindig machen können. Hier mal ein Code-Beispiel welches als 64bit kompiliert falsch rechnet.

    	double d=90000.025;
    	double a=d/25*1000; //=3600001;
    	int e = (int) a%1000;
    	ShowMessage(e); //richtig 1, falsch 0
    


  • Ich hoffe, dir ist klar, daß bei (int) a%1000 zuerst der Cast durchgeführt wird und dann erst der Integer-Modulo berechnet wird (ansonsten s. C++-Programmierung: Liste der Operatoren nach Priorität: Prio 3 <-> Prio 5).

    Und beim Cast double-> intwird immer abgerundet. Wenn nun aber der interne Wert (wegen der Ungenauigkeit bei den Fließkommazahlen) 3600000.999... beträgt, so kommt dann 3600000 heraus.
    Und weil intern verschiedene Assemblerbefehle bei 32bit vs. 64bit benutzt werden, kann man sich nicht auf die identische Berechnung verlassen.

    Um sicher zu sein, solltest du also es so berechnen:

    int e = (int)(a + .5) % 1000;
    // bzw.
    int e = static_cast<int>(a + .5) % 1000;
    


  • @Th69 sagte in Modulo falsch Berechnung?:

    int e = static_cast<int>(a + .5) % 1000;

    habe es mal ausprobiert aber leider funktioniert das nicht wie gewünscht
    denn mit einem anderem Ausgangswert kommt dann wieder ein falsches Ergebnis raus, bei 32bit und 64bit.

    double d=90000.049;
    double a=d/25*1000; //=//=3600001,96;
    int e = static_cast<int>(a + .5) % 1000;
    ShowMessage(e); // Ergebniss ist 2, ich brauche aber 1
    

    So scheint es zu funktionieren wie ich es brauche auch auf 64bit

    	double d=90000.049;
    	double a=AnsiString(d/25*1000).ToDouble(); //=3600001,96;
    	int e = (int)a%1000;
    	ShowMessage(e); //richtig 1
    
    	d=90000.050;
    	a=AnsiString(d/25*1000).ToDouble(); //=3600002;
    	e = (int)a%1000;
    	ShowMessage(e); //richtig 2
    
    	d=90000.025;
    	a=AnsiString(d/25*1000).ToDouble(); //=3600001;
    	e = (int)a%1000;
    	ShowMessage(e); //richtig 1
    
    	d=90000.024;
    	a=AnsiString(d/25*1000).ToDouble(); //=3600000,96;
    	e = (int)a%1000;
    	ShowMessage(e); //richtig 0
    

    ob ich hier explizit "static_cast<int>" benötige oder richtiger wäre weis ich nicht



  • Dann solltest du besser gleich die ganze Berechnung mit Ganzzahlen durchführen (mit String ist ja noch schlimmer).
    Woher kommt denn der Wert für d? Über eine Eingabe?
    Und hat dieser immer (max.) 3 Nachkommastellen?

    Was passiert bei:

    double d = ...;
    const long Factor = 1000;  // die 3 Nachkommazahlen nach links verschieben
    long x = static_cast<long>(d * Factor);
    long a = x * 40; // entspricht /25*1000
    
    int e = static_cast<int>((a / Factor) % 1000);
    

    s. Ideone-Code

    Edit:
    Je nach Compiler und Wertebereich von d solltest du statt long vllt. besser int64_t benutzen (das ich der Einfachheit hier benutzt habe).

    PS: static_cast<...>(...) ist ja der explizite C++ Cast-Operator, anstatt der von C übernommene Cast (type)(...).



  • @Th69 was da passiert ? mmm ich erhalte komische negative Zahlen. Ich muss auch erstmal über meinen Code etwas nachdenken, ist einfach auch alles zu lange her bin raus aus dem Thema und muss mich erstmal wieder damit mehr beschäftigen. Damals war ich ganz zufrieden das es erstmal lief, tut es auch solange die Umrechnungen auf PAL (25.00fps) basieren. Muss wahrscheinlich den Code zur Berechnung erstmal komplett überdenken dann überarbeiten denn es gibt ja nicht nur die PAL Zeiten. Die Zeiten werden aus Chapterframes berechnet oder eben entgegengesetzt (hour, min, sec, msec) zurück zu Frames. Die Berechnungen stimmen aber das Problem ist die Rundung bei nicht PAL Zeiten da diese keine Ganz-Zahlen sind und da fehlt dann gerne mal 1ms, das muss ich überarbeiten.

    Beim einladen funktioniert alles sauber die Zeiten stimmen nur meine Edit-Felder fürs editieren der double-Werte sind zu kurz 🙂 die Werte passen da nicht rein :), bei PAL reichen genau 3 Stellen hinterm Komma, aber bei NTSC müssen es 5 sein und mit der Funktion AnsiString().ToDouble() läuft das auch alles super bei 64bit.



  • Ich verstehe nicht ganz, warum du in deinen Rechnungen die Ergebnisse scheinbar erst in einen String überführst, nur um sie direkt wieder in einen Double zu konvertieren aber das will ich jetzt hier nicht zum Thema machen.
    Ich habe damit jetzt noch nicht viel Erfahrung gesammelt aber möglicherweise ist es hilfreich statt expliziten interger <-> double Konvertierungen direkt auf die std zu vertrauen und einfach std::fmod(l) zu benutzen. https://en.cppreference.com/w/cpp/numeric/math/fmod



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


Anmelden zum Antworten