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 operationThis 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, PrecisionThis 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