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

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

Sofern die Vorkommazahl nicht größer als ±264 (±18446744073709551616) ist, lasse man sich von folgendem Visual-C++-Programm inspirieren:

#pragma warning( disable : 4731 ) // warning C4731: frame pointer register 'ebp' modified by inline assembly code

int double2dez (double dubbel, char* dez)
{
    int len;

    _asm
    {
        fninit
        sub esp, 8                      ; Platz für FPU-Zwischenergebnisse

        fldz                            ; ST(0)=0
        mov word ptr [esp], 10
        fild word ptr [esp]             ; ST(0)=10 ST(1)=0

        fstcw [esp]
        or word ptr [esp], 0x0C00       ; Truncating Mode
        fldcw [esp]

        mov edi, dez                    ; Offset für alle stosb-Operationen
        mov al, byte ptr [dubbel+7]     ; oberstes Byte
        or al, al
        jns S1                          ; positiv: nichts machen
        mov al, '-'                     ;
        stosb                           ; Negativ-Vorzeichen nach dez
    S1: ; Vorkommazahl nach [esp]
        fld qword ptr [dubbel]          ; ST(0)=d        ST(1)=10     ST(2)=0
        fabs                            ; positiv
        fld st                          ; ST(0)=d        ST(1)=d      ST(2)=10 ST(3)=0
        frndint                         ; ST(0)=int(d)   ST(1)=d      ST(2)=10 ST(3)=0
        fld st                          ; ST(0)=int(d)   ST(1)=int(d) ST(2)=d  ST(3)=10 ST(4)=0
        fistp qword ptr [esp]           ; ST(0)=int(d)   ST(1)=d      ST(2)=10 ST(3)=0
        fsubp st(1), st                 ; ST(0)=d-int(d) ST(1)=10     ST(2)=0

        xchg ebp, [esp+4]               ; ebp:esi = Vorkommazahl, altes ebp sichern
        mov esi, [esp+0]
        mov ebx, 10                     ; Divisor
        xor ecx, ecx                    ; Push-Zähler
    L1: ; MOD 10 auf den Stack
        xor edx,edx
        mov eax, ebp
        div ebx
        mov ebp, eax
        mov eax, esi
        div ebx
        mov esi, eax
        push dx
        inc cl
        or esi, esi
        jne L1
        or ebp, ebp
        jne L1
    L2: ; Stack nach dez
        pop ax
        or al, 0x30                     ; ASCII
        stosb
        loop L2
        mov ebp, [esp+4]                ; altes ebp zurück

        fcomi st(0), st(2)              ; Nachkommazahl = 0 ?
        jz S2                           ; ja: Ausstieg
        mov al, '.'                     ; Dezimalpunkt nach dez
        stosb
        mov ecx, 310                    ; Maximale Anzahl an Nachkommastellen
    L3: ; Nachkommazahl nach dez
        fmul st, st(1)                  ; Eine Dezimalzahl in den Vorkommabereich holen
        fist word ptr [esp]             ; Vorkommazahl zwischenspeicherm
        fisub word ptr [esp]            ; Vorkommazahl abschneiden
        mov al, byte ptr [esp]
        or al, 0x30                     ; Integer -> ASCII
        stosb
        fcomi st(0), st(2)              ; noch was übrig?
        jz S2                           ; nein: Ausstieg
        loop L3

    S2: fninit                          ; FPU aufgeräumt zurückgeben
        add esp,8                       ; FPU-Platz wieder freigeben
        mov byte ptr [edi], 0           ; ASCIIZ-Abschlussnull

        mov eax, dez
        sub edi, eax
        mov dword ptr [len], edi
    }

	return len;
}

#pragma warning( default : 4731 ) // warning C4731: frame pointer register 'ebp' modified by inline assembly code

#include <stdio.h> // printf

int main (void)
{
    char dez[350];
    double dubbel = 987654321.3;
    int len = double2dez (dubbel, dez);

    printf ("Vorgabe: %f\n",dubbel);
    printf ("String: %s\n",dez);
    printf ("Len: %i\n",len);

    return 0;
}

Da das Programm nicht rundet, kann man damit auch Rundungsfehler der FPU darstellen.

Ralph 'rkhb' Bauer 04.08.2008