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

Wie wandle ich eine Binärzahl (String) in eine Gleitkommazahl (Double) um?

Die nachfolgende Funktion wandelt eine Binärzahl, die in Exponentialschreibweise als ASCIIZ-String vorliegt (bzw. ein String, der mit irgendeinem unzulässigen Buchstaben endet), in ein Double im Registerpaar EDX:EBX um. Die Funktion kann keine denormalisierte Gleitkommazahl herstellen. Die Binärzahl kann in folgenden Formaten vorliegen:

1010Folge von Binärziffern
-1010negative Binärzahl
1010bFolge von Binärziffern mit "unzulässigem" Zeichen als Abschluss
1010 1010Folge von Binärziffern mit Leerzeichen
1010.1010Vorkommazahl Dezimalpunkt Nachkommazahl
1010e1010Exponentialschreibweise
1010e-1010Exponentialschreibweise
Nasm
BITS 32
SEGMENT .text

%define NEGATIV     1
%define EXPMINUS    2
%define EXP         4
%define POINT       8
%define LEADZ      16
%define OVERFLOW   32
%define NULL       64
%define INFINITE  128

Bin2Double:                     ; Übergabe ESI: Zeiger auf ASCIIZ
        xor eax, eax            ; AH: Bitmaske; AL: jeweilige Binärziffer
        xor ecx, ecx            ; Exponent in der Exponentialschreibweise
        mov edi, 1023           ; Bias
        xor edx, edx            ; EDX:EBX Ergebnis
        xor ebx, ebx

    .Vorzeichen:
        lodsb
        cmp al, '+'
        je .M_Parser            ; Plus: nichts machen
        cmp al, '-'
        jne .SkipLods           ; kein Vorzeichen
        or ah, NEGATIV          ; Minus: Bitmaske setzen

   .M_Parser:
        lodsb
   .SkipLods:
        cmp al, '0'
        jb .Sonderzeichen
        jne .Eins

        test ah, LEADZ          ; führende Null?
        jnz .Verarbeitung       ; nein

        test ah, POINT          ; führende Null im Nachkommabereich?
        jz .M_Parser            ; nein: continue
        sub edi, 1              ; ja: Exponent dekrementieren
        jnc .M_Parser
        or ah, NULL             ; Unterlauf (Exponent zu klein)
        jmp .Umwandlung
    .Eins:
        cmp al, '1'
        ja .Exponent            ; keine Binärziffer: Exponentialschreibweise probieren
    .Verarbeitung:
        or ah, LEADZ
        bt edx, 20              ; 52. Bit gesetzt: Überlauf (zu viele Bits)
        jc .Overflow
        test ah, POINT
        jnz .Mantisse
        inc edi                 ; Exponent inkrementieren, wenn Vorkommazahl
        test edi, 100000000000b ; Überlauf (Exponent zu groß)
        jz .Mantisse
        or ah, INFINITE
        jmp .Umwandlung
    .Mantisse:
        shr al, 1
        rcl ebx, 1
        rcl edx, 1
        jmp .M_Parser           ; nächste Binärziffer
    .Overflow:
        test ah, OVERFLOW       ; erster Überlauf?
        jnz .SkipRound          ; nein: keine Rundung
        or ah, OVERFLOW
        shr al, 1
        adc ebx, 0              ; Rundung
        adc edx, 0
    .SkipRound:
        test ah, POINT          ; Überlauf im Nachkommabereich?
        jnz .Umwandlung         ; ja: Ende
        inc edi                 ; nein: Exponent erhöhen, sonst nichts
        test edi, 100000000000b ; Überlauf (Exponent zu groß)
        jz .M_Parser
        or ah, INFINITE
        jmp .Umwandlung

    .Sonderzeichen:
        cmp al, 32              ; Leerzeichen?
        je .M_Parser            ; ja: nächste Binärziffer
        cmp al, 46              ; Dezimalpunkt
        jne .Umwandlung         ; nein: Ausstieg
        test ah, POINT
        jnz .Umwandlung         ; Dezimalpunkt bereits gefunden
        or ah, POINT
        jmp .M_Parser

    .Exponent:
        cmp al, 'e'
        je .E1
        cmp al, 'E'
        jne .Umwandlung
    .E1:
        test ah, EXP
        jnz .Umwandlung           ; Expontialzeichen bereits gefunden
        or ah, EXP
        lodsb
        cmp al, '+'
        je .E_Parser              ; Plus: nichts machen
        cmp al, '-'
        jne .E_SkipLods           ; kein Vorzeichen
        or ah, EXPMINUS           ; Minus: Bitmaske setzen

    .E_Parser:
        lodsb
    .E_SkipLods:
        cmp al, 32
        je .E_Parser
        cmp al, '0'
        jb .Umwandlung
        cmp al, '1'
        ja .Umwandlung

        shr al, 1
        rcl ecx, 1
        jnc .E_Parser

    .E_Overflow:
        test ah, EXPMINUS
        jnz .E2
        or ah, INFINITE
        jmp .Umwandlung

    .E2:
        or ah, NULL
        jmp .Umwandlung

    .Umwandlung:                ; EBX:EDX: Mantisse; EDI: normalisierter Exponent; ECX: angegebener Exponent
        dec edi

        test ah, EXP
        jz .SkipExp
        test ah, EXPMINUS
        jz .ExpPlus
        sub edi, ecx
        jnc .SkipExp
        or ah, NULL
        jmp .SkipExp
    .ExpPlus:
        add edi, ecx
        test edi, 100000000000b ; Überlauf (Exponent zu groß)
        jz .SkipExp
        or ah, INFINITE
    .SkipExp:

        test ah, NEGATIV
        jz .SkipMinus
        or edi, 100000000000b   ; Negative Zahl: Vorzeichen-Bit setzen
    .SkipMinus:

        test ah, NULL
        jz .SkipNil
        and edi, 100000000000b  ; Exponent=0, Mantisse=0, Vorzeichen bleibt
        xor edx, edx
        xor ebx, ebx
        jmp .Ende
    .SkipNil:

        test ah, INFINITE
        jz .SkipInfinite
        or edi, 11111111111b   ; Exponent=(-1), Mantisse=0
        xor edx, edx
        xor ebx, ebx
        jmp .Ende
    .SkipInfinite:

        mov ecx, 31
        bsr eax, edx
        jnz .SkipNull           ; gesetztes Bit in EDX gefunden
        xchg edx, ebx           ; EDX=EBX, EBX=0
        bsr eax, edx
        jnz .SkipNull           ; gesetztes Bit in EBX gefunden
        and edi, 100000000000b  ; Sonderfall Null (Exponent=0, Mantisse=0, Vorzeichen bleibt)
        xor edx, edx
        xor ebx, ebx
        jmp .Ende
    .SkipNull:

        sub ecx, eax            ; alles nach ganz links shiften
        shld edx, ebx, cl
        shl ebx, cl

        shld edx, ebx, 1        ; führende Eins verschwindet
        shl ebx, 1

    .Ende:
        shrd ebx, edx, 12       ; Vorzeichen und BIAS von rechts hineinshiften
        shrd edx, edi, 12

        ret                     ; Ergebnis EDX:EBX: Double

Ralph 'rkhb' Bauer Aug 2008