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 BCD 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 preceding 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