Simply FPU Chap. 6

SIMPLY FPU
by Raymond Filiatreault
Copyright 2003

Chap. 6
Data transfer instructions - Packed decimals

The FPU instructions covered in this chapter perform no mathematical operation on numerical data. Their main purpose is simply to transfer packed Binary Coded Decimal (BCD) integer data between the FPU's 80-bit data registers and memory.

There are only two FPU instructions using packed BCD data as the operand and no other data type can be used with those instructions. Both have the letter "B" immediately following the "F" in the mnemonic.

    FBLD   LoaD BCD data from memory

    FBSTP  STore BCD data to memory

FBLD (Load BCD data from memory)

    Syntax:    fbld Src

    Exception flags: Stack Fault, Invalid operation

This instruction decrements the TOP register pointer in the Status Word and loads the packed BCD integer value from the specified source (Src) in the new TOP data register ST(0) after converting it to the 80-bit REAL10 format. The source must be the memory address of an 80-bit TBYTE having the specified packed BCD format. (See Chap.2 for the packed BCD format and its addressing modes).

If the ST(7) data register which would become the new ST(0) is not empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The TOP register pointer in the Status Word would still be decremented and the new value in ST(0) would be the INDEFINITE NAN.

The content of the Tag Word for the new TOP register will be modified according to the result of the operation.

Although each nibble (4 bits) in a proper packed BCD format should not exceed a value of 9, the FPU will convert any value without any exception being detected. If a nibble does exceed a value of 9, the FPU simply uses its mod 10 value and carries a 1 to the next nibble. For example,

if a TBYTE contains the hex value of 00000000000000009A7Dh,

the FBLD instruction would convert it as the decimal value of 10083d

or the same as a proper packed BDC value of 00000000000000010083h.

The FBLD instruction is used primarily for converting alphanumeric strings (which usually contain a fractional part) to the REAL10 format. The process involves

- parsing the string for invalid characters to avoid processing "garbage",
- determining the number of decimal places,
- converting the digits to the packed BCD format,
- loading the formatted number to the FPU as an integer, and
- dividing by the appropriate exponent of 10 according to the number of decimal places in the original string.

Following is an example of code to convert a decimal string to the packed BCD format. This example would only allow a string in the regular decimal notation. Separate instructions would be needed to also accept a string in the scientific notation. (Code is not included for the final steps of loading it to the FPU and dividing by an exponent of 10 to arrive at the final floating point value.)

************

//The packed BCD value will be stored in a global memory variable. A local variable or some other memory location could be used as well for this purpose. The same applies for the location where the null-terminated "user input" string will be available.

This code will return the packed BCD value in memory, the total number of integer/decimal digits in AH and the number of decimal digits in AL. If an error is detected (no input digit, more than 18 digits, invalid character), EAX will return with 0, and ECX will contain the counts of digits.//

    .data

    temp_bcd     dt   ?
    szdecimal    db   32 dup(0)

    .code

    a2bcd proc

    ; EDI will be used as the pointer for the location of the BCD value
    ; ESI will be used as the pointer to the source decimal string
    ; ECX will be used to hold the count of integer digits (in CH)
    ; and decimal digits (in CL)

       push esi
       push edi
       xor  ecx,ecx         ;initialize for the counts
       lea  esi,szdecimal-1 ;point to byte preceeding the source string
       lea  edi,temp_bcd    ;point to the memory location for the packed BCD

       xor  eax,eax
       stosd
       stosd
       stosw                ;initializes the TBYTE to 0, including the sign byte
       dec  edi             ;adjust for pointing to the sign byte
                            ; (most significant byte)
                            ;which now assumes the value as positive

    ; The following is to skip leading spaces without generating an error

    space_check:
       inc  esi             ;point to next character
       mov  al,[esi]        ;get character
       cmp  al," "          ;is it a space
       jz   space_check     ;check for another space

    ; Check 1st non-space character for + or - sign

       .if  al == "-"       ;is there a "-" sign
          mov  byte ptr [edi],80h ;changes the sign byte for the negative code
          inc  esi          ;point to next character

       .elseif al == "+"    ;is there a "+" sign
          inc  esi          ;point to next character,
                            ; the sign byte is already coded positive
       .endif

    integer_count:
       .if  al == 0         ;is it the end of the source string
          jmp count_end
       .elseif al == "."    ;is it a "period" decimal delimiter
          jmp decimal_count
       .elseif al == ","    ;is it a "coma" decimal delimiter
                            ; (also used in numerous countries)
          jmp decimal_count
       .elseif al < "0"     ;is it an invalid character below the ASCII 0
          jmp input_error
       .elseif al > "9"     ;is it an invalid character above the ASCII 9
          jmp input_error
       .elseif ch == 18     ;is digit count already at the maximum allowed
          jmp input_error
       .endif

       inc  ch              ;increment the count of integers digits
       inc  esi             ;point to next character
       mov  al,[esi]        ;get character
       jmp  integer_count   ;continue counting integer digits

    decimal_count:
       inc  esi             ;point to next character
       mov  al,[esi]        ;get character
       .if  al == 0         ;is it the end of the source string
          jmp count_end
       .elseif al < "0"     ;is it an invalid character below the ASCII 0
          jmp input_error
       .elseif al > "9"     ;is it an invalid character above the ASCII 9
          jmp input_error
       .elseif ch == 18     ;is digit count already at the maximum allowed
          jmp input_error
       .endif

       inc  cl              ;increment the count of decimals digits
       inc  ch              ;increment the count of integers+decimals digits
       jmp  decimal_count   ;continue counting decimal digits

    count_end:
       .if  ch == 0         ;check if input has any digit
          jmp input_error
       .endif

    ; At this point, the input string is valid,
    ; CL contains the count of decimal digits,
    ; CH contains the total count of decimal+integer digits, and
    ; ESI points to the terminating 0.
    ; All the digits now have to be converted to binary and
    ; packed two per byte from the least significant to the more significant

       push ecx            ;for temporary storage of the counts
       sub  ch,cl          ;CH now contains the count of only the integer digits
       lea  edi,temp_bcd   ;point to the least significant byte of the packed BCD

    pack_decimals:
       dec  esi            ;point to the next more significant digit
       mov  al,[esi]       ;get decimal character

       .if  cl == 0        ;check if decimal digits all done
          .if  al < "0"    ;checks if current character is the decimal delimiter
             dec  esi      ;skip the decimal delimiter
          .endif
          jmp  pack_integers
       .endif

       and  al,0fh         ;keep only the binary portion
       ror  ax,4           ;transfer the 4 bits to the H.O. nibble of AH
       dec  esi            ;point to the next more significant digit
       dec  cl             ;decrement counter of decimal characters

       .if  cl == 0        ;check if decimal digits all done
          dec  esi         ;skip the decimal delimiter
          jmp  pack_integer2 ;get an integer character as 2nd nibble of this byte
       .endif

       mov  al,[esi]       ;get decimal character
       and  al,0fh         ;keep only the binary portion
       rol  ax,4           ;moves the 2nd binary value to the H.O. nibble of AL
                           ;and retrieves the 1st binary value from AH
                           ; into the L.O. nibble of AL
       stosb               ;store this packed BCD byte and increment EDI
       dec  cl             ;decrement counter of decimal characters
       jmp  pack_decimals  ;continue packing the decimal digits

    pack_integers:
       .if  ch == 0        ;check if integer digits all done
          jmp  all_done    ;and terminate the packing process
       .endif

       mov  al,[esi]       ;get integer character
       and  al,0fh         ;keep only the binary portion
       ror  ax,4           ;transfer the 4 bits to the H.O. nibble of AH
       dec  esi            ;point to the next more significant digit
       dec  ch             ;decrement counter of integer characters

    pack_integer2:
       .if  ch == 0        ;check if integer digits all done
          rol  ax,4        ;moves the 2nd binary value to the H.O. nibble of AL
                           ;and retrieves the 1st binary value from AH
                           ; into the L.O. nibble of AL
          mov  [edi],al    ;store this packed BCD byte
          jmp  all_done    ;and terminate the packing process
       .endif

       mov  al,[esi]       ;get integer character
       and  al,0fh         ;keep only the binary portion
       rol  ax,4           ;moves the 2nd binary value to the H.O. nibble of AL
                           ;and retrieves the 1st binary value from AH
                           ; into the L.O. nibble of AL
       stosb               ;store this packed BCD byte and increment EDI
       dec  esi            ;point to the next more significant digit
       dec  ch             ;decrement counter of integer characters
       jmp  pack_integers  ;continue packing the integer digits

    all_done:
       pop  eax            ;retrieve the counts which were in ECX
       pop  edi
       pop  esi
       ret

    input_error:
       xor  eax,eax        ;to return with error code
       pop  edi
       pop  esi
       ret

    a2bcd endp

************

FBSTP (Store BCD data to memory)

    Syntax:    fbstp Dest

    Exception flags: Stack Fault, Invalid operation, Precision

This instruction rounds the value of the TOP data register ST(0) to the nearest integer (regardless of the rounding mode of the RC field in the Control Word), converts it to the packed BCD format, and stores it at the specified destination (Dest). (See Chap.2 for the packed BCD format and its addressing modes). It then POPs the TOP data register ST(0), modifying the Tag Word and incrementing the TOP register pointer of the Status Word.

> If the floating point value needs to be rounded according to some other mode, the FRNDINT instruction must be used prior to the FBSTP instruction.

If the ST(0) data register is empty, both a Stack Fault and an Invalid operation exceptions are detected, setting both flags in the Status Word. The INDEFINITE value in REAL10 format (FFFFC000000000000000h) would be stored as such at the specified destination.

If the rounded value of ST(0) would contain more than 18 integer digits, an Invalid operation exception is detected, setting the related flag in the Status Word. The INDEFINITE value in REAL10 format would be stored as such at the specified destination.

If the value in ST(0) would contain a binary fraction, some precision would be lost and a Precision exception would be detected, setting the related flag in the Status Word.

The FBSTP instruction is used primarily for converting floating point values from ST(0) to alphanumeric strings (usually with a required number of decimal places). The process involves:
- multiplying the value in ST(0) by an appropriate exponent of 10 according to the number of required decimal places, (taking precautions not to exceed 18 significant decimal digits in the resulting integer portion),
- storing it to a memory location as a packed BCD format,
- converting from the packed BCD format to a null-terminated alphanumeric string.

> If the value to be converted is ≥1019 or <1, it should generally be converted to the scientific notation. The exception may be for small values close to 1 which need to be reported only to a few decimal places.

Following is an example of code to convert from the packed BCD format to a decimal string in regular notation (some modification would be needed to prepare a decimal string in scientific notation).

************

//The packed BCD value will be stored in a global memory variable. A local variable or some other memory location could be used as well for this purpose. The same applies for the location where the resulting null-terminated decimal string will be stored.

This code assumes that the floating point value is currently in the FPU's ST(0) register. It also assumes that 2 decimal places are required and that the resulting decimal value will not contain more than 18 digits. No error checking is performed.

The decimal string will also be left-justified. A "-" sign will be inserted as the first character if the value is negative, but nothing is inserted if the value is positive. Those are all additional options for the programmer.//

.data

    temp_bcd     dt   ?
    szdecimal    db   32 dup(0)

    .code

    bcd2a PROC

       pushd 100            ;use the stack as storage for this value
       fimul dword ptr[esp] ;converts 2 decimal places as 2 more integer digits
       fbstp temp_bcd       ;store the packed BCD integer value
       pop  eax             ;clean the stack of the pushed 100

    ; ESI will be used for pointing to the packed BCD
    ; EDI will be used for pointing to the decimal string

       push esi
       push edi
       lea  esi,temp_bcd+9  ;point to the most significant byte
       lea  edi,szdecimal   ;point to the start of the decimal string buffer
       xor  eax,eax
       fwait                ;to ascertain that the transfer of the
                            ;packed BCD is completed

       mov  al,[esi]        ;get the byte with the sign code
       dec  esi             ;decrement to next most significant byte
       or   al,al           ;for checking the sign bit
       jns  @F              ;jump if no sign bit
       mov  al,"-"          ;the value is negative
       stosb                ;insert the negative sign

    @@:

    ; The next 8 bytes (in this example) will contain the integer digits
    ; and the least significant byte will then contain the 2 decimal digits.
    ; No leading 0 will be included in the integer portion
    ; unless it would be the only integer digit.

       mov  ecx,8           ;number of bytes to process for integer digits

    @@:

       mov  al,[esi]        ;get the next byte
       dec  esi             ;adjust the pointer to the next one
       or   al,al           ;for checking if it is 0
       jnz  @F              ;the starting integer digit is now in AL
       dec  ecx             ;adjust the counter of integer bytes
       jnz  @B              ;continue searching for the first integer digit

    ; If the loop terminates with ECX=0, the integer portion would be 0.
    ; A "0" character must be inserted before processing the decimal digits

       mov  al,"0"          ;the ASCII 0
       stosb                ;insert it
       mov  al,[esi]        ;get the byte containing the decimal digits
       jmp  decimal_digits

    @@:

    ; The first integer byte must be checked to determine
    ; if it contains 1 or 2 integer digits

       test al,0f0h         ;test if the H.O. nibble contains a digit
       jz   int_digit2      ;if not, process only the L.O. nibble

    int_digit1:
       ror  ax,4            ;puts the H.O. nibble in the L.O. nibble position
                            ;and saves the L.O. nibble in AH
       add  al,30h          ;convert it to ASCII character
       stosb                ;store this character
       shr  ax,12           ;restores the L.O. nibble in AL
                            ;and also resets the other bits of AX

    int_digit2:
       add  al,30h          ;convert it to ASCII character
       stosb                ;store this character
       mov  al,[esi]        ;get next byte
       dec  esi             ;adjust the pointer to the next one
       dec  ecx             ;adjust the counter of integer bytes
       jnz  int_digit1      ;continue processing the integer bytes

    decimal_digits:
       mov  byte ptr [edi],"." ;insert the preferred decimal delimiter
       inc  edi

    ; If more than 2 decimal digits are required, a counter would be needed
    ; to process the necessary bytes.
    ; Also, if the number of required decimal digits is not even, the code
    ; would have to be altered to insert the decimal delimiter at the
    ; proper location.

       ror  ax,4            ;puts the H.O. nibble in the L.O. nibble position
                            ;and saves the L.O. nibble in AH
       add  al,30h          ;convert it to ASCII character
       stosb                ;store this character
       shr  ax,12           ;restores the L.O. nibble in AL
                            ;and also resets the other bits of AX
       add  al,30h          ;convert it to ASCII character
       stosw                ;store this character + the terminating 0

       pop edi
       pop esi
       ret

    bcd2a ENDP

************

RETURN TO
SIMPLY FPU
CONTENTS