FAQ der Newsgroup de.comp.lang.assembler (d.c.l.a.)

Wie wandle ich eine Dezimalzahl (String) in eine Gleitkommazahl (Double) um?

Gegeben sei ein String "18.4", der während der Laufzeit des Programms von irgendwoher bezogen wurde (z.B. von einem Messgerät). Dieser soll in eine Gleitkommazahl umgewandelt werden, mit der dann weitergerechnet werden kann.

Die eigentliche Umwandlung kann (und sollte) man der FPU überlassen. Allerdings kann die FPU nur Gleitkommazahlen, BCD-Zahlen und Integerzahlen einlesen. Am geschicktesten ist es, den String in einer Art und Weise in eine oder mehrere Integerzahlen umzuwandeln, die dann von der FPU eingelesen und weiterverarbeitet werden können.

Der erste Schritt ist, den String in eine sogenannte "dezimale Gleitkommazahl" (decimal floating point number) umzuwandeln, also in eine Zahl ohne Komma, aber mit Exponent: Aus 18.4 soll 184e-2 (184 * 10-2) werden. Ein Beispiel in Visual-C++:

typedef struct {
    int   mantisse;
    short exponent;
} DECIMALFLOAT;

DECIMALFLOAT dez2dfn (const char* input)
{
    DECIMALFLOAT dfn;

    #define DEZMINUS 1
    #define EXPMINUS 2
    #define EXP      4
    #define POINT    8

    __asm
    {
            xor ebx, ebx                       ; BH: Bitmaske, BL: Buchstabe
            xor ecx, ecx                       ; Exponent
            mov esi, input                     ; Zeiger auf Dezimalstring
            mov dword ptr [dfn.mantisse], ebx  ; dfn = {0,0}
            mov word ptr [dfn.exponent], bx

            mov bl, byte ptr [esi]             ; 1. Buchstabe = Vorzeichen?
            cmp bl, '+'
            je Schleife                        ; Plus: nichts machen
            cmp bl, '-'
            jne S1                             ; kein Vorzeichen
            or bh, DEZMINUS                    ; Minus: Bitmaske setzen

        Schleife:
            inc esi
            mov bl, byte ptr [esi]

        S1: cmp bl, '0'
            jb S5                              ; Punkt, Stringende oder Fehler
            cmp bl, '9'
            ja S6                              ; 'e', 'E' oder Fehler

            and bl, 0x0F                       ; Umwandlung: Buchstabe → Zahl
            test bh, EXP
            jne S3

            imul eax,dword ptr [dfn.mantisse],10   ; eax = Mantisse*10 + Ziffer
            jo S8                              ; Wertebereich überschritten
            add al, bl
            jnc S2
            add eax, 0x0100
            jo S8                              ; Vorzeichenwechsel Plus → Minus
            jc S8                              ; Vorzeichenwechsel Minus → Plus
        S2: mov dword ptr [dfn.mantisse], eax
            test bh, POINT
            je Schleife                        ; noch kein Dezimalpunkt erkannt
            dec word ptr [dfn.exponent]
            jmp Schleife

        S3: imul cx,cx,10                      ; Exponent = Exponent * 10 + Ziffer
            jo S8                              ; Wertebereich überschritten
            add cl, bl
            jnc S4
            add cx, 0x0100
            jo S8                              ; Vorzeichenwechsel Plus->Minus
            jc S8                              ; Vorzeichenwchsel Minus->Plus
        S4: jmp Schleife

        S5: test bl, bl
            jz S9
            cmp bl, '.'
            jne S8                             ; ASCII-Wert < '0' aber kein Punkt
            test bh, EXP | POINT
            jne S8                             ; Punkt nach Punkt oder 'e' unzulässig
            or bh, POINT
            jmp Schleife
        S6: cmp bl, 'e'
            je S7
            cmp bl, 'E'
            jne S8                             ; ASCII-Wert > '9' aber kein 'e','E'
        S7: test bh, EXP
            jne S8                             ; Zweimal 'e' unzulässig
            or bh, EXP
            inc esi
            mov bl, [esi]                      ; nächster Buchstabe Vorzeichen?
            cmp bl, '+'
            je Schleife                        ; Plus: nichts machen
            cmp bl, '-'
            jne S1                             ; kein Vorzeichen
            or bh, EXPMINUS                    ; Minus: Bitmaske setzen
            jmp Schleife
        S8:
            xor eax, eax                       ; Fehler: Ausstieg vor Stringende
            mov dword ptr [dfn.mantisse], eax
            mov word ptr [dfn.exponent], ax
            jmp S12
        S9:
            test bh, DEZMINUS
            jz S10
            neg dword ptr [dfn.mantisse]
        S10:
            test bh, EXPMINUS
            jz S11
            neg cx
        S11:
            add word ptr [dfn.exponent], cx
        S12:
    }

    return dfn;
}

Nun kann man die FPU mit den Zahlen füttern und umwandeln lassen:

double dfn2double (DECIMALFLOAT dfn)
{
    double dubbel;

    __asm
    {
            push 10                            ; Faktor 10 in die FPU
            fild dword ptr [esp]
            add esp, 4
            fild dword ptr [dfn.mantisse]
            movsx ecx, word ptr [dfn.exponent]
            test ecx, ecx
            jz S3                              ; Sprung, wenn ecx=0
            jns S1                             ; Sprung, wenn ecx positiv
            neg ecx

        S1: fdiv st, st(1)                     ; Exponent < 0: Komma nach links versetzen
            loop S1
            jmp S3

        S2: fmul st, st(1)                     ; Exponent > 0: Komma nach rechts versetzen
            loop S2

        S3: fstp qword ptr [dubbel]
            fninit                             ; FPU aufräumen
    }

    return dubbel;
}

Zum Testen noch schnell eine Main-Funktion gebastelt:

#include <stdlib.h> // strtod
#include <stdio.h>  // printf

void main (void)
{
    DECIMALFLOAT dfn;
    char * Eingabe, * stopstring;
    Eingabe = "-18.4e-4";
    double dubbel = 0;
    double testdubbel = strtod (Eingabe,&stopstring);

    printf ("Eingabe: %s\n", Eingabe);
    dfn = dez2dfn (Eingabe);
    printf ("Mantisse: %i   Exponent: %i\n",dfn.mantisse,dfn.exponent);
    dubbel = dfn2double (dfn);
    printf ("dubbel:     %e\n",dubbel);
    printf ("testdubbel: %e\n",testdubbel);
}

Ralph 'rkhb' Bauer Aug 2008