Mit Fließkommazahlen arbeiten



  • Hallo zusammen,

    in der Regel programmiere ich in C/C++, aber um Programme zu beschleunigen, wollte ich einige Befehle als inline-Assembler schreiben. Das hat sogar schon geklappt und zwar mit der sin- und cos-Funktion, beim arctan bekomme ich aber mächtig Probleme, weil ich beim Befehl fpatan zwei Argumente habe.
    Sinuns und Cosinus bekomme ich mit
    asm("fsincos" : "=t" (c_wert), "u" (s_wert) : "0" (argument) );
    hin.
    Jetzt muss ich der Kiste für den fpatan (in C ist das atan2) zwei Argumente übergeben, und zwar in st(0) und st(1). Das Internet ist mir überhaupt keine Hilfe. Sind das nun die "normalen" Stackregister, oder sind st(0) bis st(7) spezielle Register im Zusammenhang mit der FPU?
    Dann habe ich probiert, Fließkommazahlen in Register zu schreiben, aber weder "mov eax 5.432" noch "mov eax _doublevariable" funktionieren.
    Da ärgere ich mich schon, dass praktisch alle Internet-Beispiele mit integer-zahlen sind und keine es für nötig hält, mal ein paar Sätze über Fließkomma-Sachen zuschreiben.
    Selbst bei der Forensuche HIER gibt es beim Suchwort "float" Null Treffer!
    Dann ahbe ich mich gefragt, ob ich jetzt die Werte mit "fld" oder "push" oder was anderes auf den Stack schreiben soll.
    Mein Gott, habe ich da Zeit verschwendet.
    Schließlich funktionerte in einem Testprogramm folgender Code:
    asm("fld %1; fld %2, fpatan; fstp %o;" "_g" (ergebnis): "g" (y), "g" (x));
    Das geht sowohl mit, als auch ohne finit.

    Im eigentlichen Programm spuckt der gleiche Programmcode kein vernünftiges Ergebnis aus, obwohl ich die Variablen sogar global deklariert habe.

    Irgendwie habe ich schon 3 Stunden verbraucht und scheine eine großen Denkfehler zu machen, aber das Internet gibt entweder nur einfaches integer-Zeug her und wirklich komplizierte Sachen für Informatikstudenten. Im Schwierigkeitsgrad zwischendrin ist nichts zu finden.

    Was mache ich falsch?



  • Primärliteratur: Dokumentation der Hersteller Intel und AMD
    ansonsten z.B.: http://www.ray.masmcode.com/fpu.html
    Wenn nur die 4+1 Grundrechenarten (+-*/sqrt) benötigt werden und 24, bzw. 53 Bit Präzision genug sind, ist die Benutzung der skalaren SSEx Befehl empfehlenswert.



  • Die CPU und die FPU waren früher zwei verschiedene Bauteile. Die FPU sogar oft nur optional und so gab es mehr oder weniger schneller Softwareemulationen.

    Die Register der FPU sind im Stapel organisiert, so kann man auch sagen, Stackprozessor.

    SSE Befehle sind Multimediaerweiterungen, mit typischen Hilfen für Grafikgeschichten, die aber wirken vor der Leistung moderner Grafikkarten albern.
    Nichtsdestotrotz, sind aber für dieses oder jenes ganz nützlich.
    Um einigermaßen kompatibel zu bleiben, sind FPU und die SSE-Erweiterungen in der Implementierung Entweder-Oder ein Kompromiß.

    Um mit Integerregistern Kommazahlen hinzubekommen, brauchst du mindestens den Ausgabecode für das Kommazeichen oder du überlegst die eine Ausgabealternative.
    Für größere Zahlen und Reste werden EDX:EAX kombiniert.

    ...aber das geht im einzelnen etwas zu weit, es gibt ganz gute FPU Programmiertexte im Internet, aber es empfiehlt sich auch ein Buch und eine Internetseite zum Assemblerprogrammieren:
    Buch:
    "Assembler" von Rohde und Roming
    Internetseite:
    http://www.website.masmforum.com/tutorials/fptute/



  • uerst einmal Danke für die Antworten.

    die beiden Links zeigen alle auf das gleiche Dokument. Ich habe versucht, die Informationen nachzuvollziehen, allerdings stehe ich irgendwie auf der Leitung. Das sieht irgendwie wie eine Mathe-Vorlesung aus: Definition, Satz,Beweis, Definition, Satz ,Beweis, Definition, Satz , Beweis, OHNE ein nachvollziehbares Beispiel. Didaktisch eher am unteren Bereich angesiedelt.

    Die Regsiter st(0) bis st(7) werdne zyklisch gefüllt, bzw. entleert. (Ist das so Okay?)
    Wenn das Register gefüllt ist, dann muss es erst entleert werden, bevor es wieder voll gemacht werden kann, oder?
    Was nehme ich zum lerren pop, fst fstp, ...? Keine Ahnung, kein Beispiel, keine Hilfe...
    Wenn ich versuche, so ein Regsiter mit fstp oder fst zu entleeren, dann gibt es keinen Rückgabewert. Die Variable, die einen neuen Wert erhalten soll, behält einfachen ihren alten Wert, komisch.
    Und bei pop gibt es eine Fehlermeldung.
    Ich kann mit dem Befehl, fld1 und fldpi, sowohl den Wert 1 als auch den Wert pi laden, ohne Probleme, aber Werte von Variablen dort hineinzuladen, klappt irgendwie nicht.
    Es kommt sogar ein vernünftiger Wert für den atan heraus, aber mit Variablen geht es gar nicht.
    In dieser Anleitung steht was von real4, real8 und real10- Werten, aber wenn ich das mit einer Inline-Assebmler-Anweisung machen möchte, gibt es ja in C, wie in vielen anderen Programmiersprachen, nur einfache und doppelte Genauigkeit für Fließkommazahlen.
    Da fehlt mir einfach der Bezug zur Praxis oder irgendein Beispiel.
    Derjenige, der den Text verfasst hat, MUSS es doch wissen:
    Die Leute fangen mit Programmieren an, da gibt es integer und float und dann auch double. Da muss man doch als Verfasser des einziges verfügbaren Dokumentes zum FPU-Assembler mal einen Satz dazu schreiben, wie ich daraus ein 80bit-Gleitkommazahl mache. Ist das zuviel verlangt?

    Kann mir jemand helfen. Ihr seht doch, dass ich mich bemühe und meine Fragen doch sehr konkret, oder seid Ihr alle Integer-Yunkis?

    Viele Grüße



  • Wie wäre es den erst einmal mit ein richtigen Assembler (z.B. NASM, FASM, MASM/JWASM) anzufangen anstelle dieser Inline frickelei?
    Für diese Assembler gibt es massig Beispiel zur FPU Benutzung - einfach mal in den entsprechenden Foren rumstöbern.

    BTW: REAL4=float=binary32, REAl8=double=binary64



  • Du versuchst Dich an Inline-Assembler des GCC. Das ist recht trickreich und eigentlich nicht für den Endprogrammierer vorgesehen. Als allererstes solltest Du Dich von der AT&T-Syntax verabschieden und mit Intel-Syntax arbeiten. Das geht mit dem GCC-Schalter -masm=intel. Hier ein funktionierendes Programm für GCC:

    #include <stdio.h>
    
    double fl = 1.234567;
    
    double multiply ( double mul1, double mul2 )
    {
        double result = 0.0;
    
        asm
        (
            "fld qword ptr %[mul1]      \n"         // ST[0] = mul1
            "fmul qword ptr %[mul2]     \n"         // ST[0] *= mul2
            "fstp qword ptr %[result]   \n"         // result = ST[0], ST[0] = empty
            : [result] "=m" (result)                // Output-Operanden
            : [mul1] "m" (mul1), [mul2] "m" (mul2)  // Input-Operanden
        );
    
        return result;
    }
    
    __attribute__((noinline)) double subtract ( ) // klappt nur OHNE Optimierung
    {
        double result = 0.0;
    
        asm
        (
            "fld qword ptr [ebp+8]      \n"     // ST[0] = erstes Argument auf dem Stack
            "fsub qword ptr [ebp+16]    \n"     // ST[0] -= zweites Argument auf dem Stack
            "fstp qword ptr [ebp-8]     \n"     // result = ST[0], ST[0] = empty
        );
    
        return result;
    }
    
    int main (void)
    {
        printf("%f\n",fl);              // 1.234567
    
        asm
        (
            "fld1               \n"     // ST[0] = 1.0
            "fld qword ptr _fl  \n"     // ST[0] = fl, ST[1] = 1.0, Unterstrich beachten!
            "faddp              \n"     // ST[0] += ST[1], ST[1] = empty
            "fstp qword ptr _fl \n"     // fl = ST[0], ST[0] = empty
        );
    
        printf("%f\n",fl);              // 2.234567
    
        double mul = multiply (2.0,3.0);
        printf ("%f\n",mul);            // 6.000000
    
        double sub = subtract (3.78, 2.56);
        printf ("%f\n",sub);            // 1.220000
    
        return 0;
    }
    

    Du musst immer die Register im Auge behalten, weil sie wandern können. Der Inhalt von ST[0] kann also zu ST[1], ST[2] usw. werden. Mit einem Debugger kannst Du diese Wanderungen schrittweise verfolgen. Normalerweise wird die FPU gepusht und gepopt, Werte werden also oben auf den Stapel gelegt und von oben wieder gelöscht. Wenn Du die Werte auf dem Stapel lässt und munter weiter pusht, ist der Stapel bald voll und Du bekommst einen Absturz.

    Spiel mal mit meinem Programm und stelle dann Fragen konkret mit Codebeispiel.

    HTH
    viele grüße
    ralph


Log in to reply