From : Julian Schmidt - BCD Tutor Here's a bit of tutorial information for Assembly Language Programmers: If anyone notices any errors, omissions, or flat-out lies, please correct me... This text is in the public domain. Q: What is the BCD number format and why should I care about it? A: BCD stands for Binary Coded Decimal. It's a number format that assembly programmers occasionally need to deal with since some BIOS functions return values as BCD. BCD uses 4-bit binary representation for the decimal numbers 0 to 9. Since each BCD numeral is encoded with 4 bits, it is possible to store two BCD numbers in a byte with the most significant digit stored in the high-order 4 bits. This format is known as packed BCD. Packed BCD is also referred to as 'packed decimal' or 'decimal integer'. A format known as unpacked BCD uses the low-order four bits of a byte to store a single BCD digit and wastes the high-order nybble. Unpacked BCD is also called 'unpacked decimal' or 'ASCII integer'. A comparison of Packed versus Unpacked BCD formats: Unpacked BCD Format Packed BCD Format ----------------- ------------------- memory memory memory memory contents address contents address 0000 0101 X+3 0000 0001 X+2 0000 0101 X+1 0101 0001 X+1 0000 0000 X 0101 0000 X Both formats encode the decimal number 5150d, but the unpacked BCD requires twice the storage space as does the packed BCD. Arithmetic on unpacked BCD numbers can be performed by the 80XXX microprocessors. Unfortunately, the arithmetic instructions available for unsigned binary integers will often result in errors when used with BCD numbers. Consider the following calculation: 0000 1001 + 0000 0011 = 0000 1100 9 + 3 = 12 This result is, of course, correct if we interpret it as a binary integer but 1100 is not a valid BCD digit. Furthermore, consider this addition: 0000 1001 + 0000 1000 = 0001 0001 9 + 8 = 17 The low-order 4 bits contain a valid BCD digit, but the result is incorrect (in terms of BCD representation). Fortunately, the 80XXX microprocessor has several features that enable us to correct for these errors. Whenever there is a carry out of, or borrow into, the low-order 4 bits of a byte, the auxiliary carry flag (AF) is set to 1. If no carry out, or borrow into, occurs, AF is set to zero. Therefore, the 80XXX utilizes a method of 'post facto' adjustement to correct the results of BCD arithmetic. The instructions AAA and DAA are provided to adjust the results of BCD addition. They are used immediately after the ADD instruction. AAA operates on the result, in register AL, an addition of two unpacked BCD numbers. DAA operates on the result, in AL, of an addition of two packed BCD numbers. In the interest of brevity, I will only explain AAA here. ;Add two BCD numbers, 9 + 3: Number1 db 00001001b ;A byte data item containing unpacked BCD. Number2 db 00000011b ;" mov al,Number1 ;Put the number into register AL. add al,Number2 ;Add two numbers, result in AL. ;Now, AL = 00001100b which is not a valid BCD number. aaa ;Issue the Ascii Adjust for Addition instruction. ;Now, AX = 00000001 00000010b which is valid unpacked BCD. AAA determines if the contents of AL are greater than 9 (which indicates an invalid unpacked BCD number), or if the AF has been set, and if so, adds an adjustment factor (00000110b) to the low-order four bits of AL, clears the high-order four bits of AL, a 1 is added to AH, and the CF and AF flags are set to 1. Multiple precision BCD arithmetic can also be performed by a series of ADDs and AAAs on each digit of the two respective numbers. Subtraction of packed or unpacked BCD numbers is handled with the AAS and DAS instructions. I won't go into detail here. Multiplication and division of packed BCD numbers is not supported by the 80XXX. If it is necessary to perform these operations, the numbers must converted to unpacked BCD. I won't discuss multiplication or division here, since they are considerably more involved than simple addition and subtraction, except to say that the AAM (ASCII Adjust for Multiply) and AAD (ASCII Adjust for Divide) are provided by the 80XXX to adjust MUL and DIV operations for unpacked BCD numbers. If extensive BCD arithmetic needs to be performed, my recommendation is to convert the BCD numbers to integers, and then use the 80XXX to perform the math. The result can then be converted back into BCD. If an 80X87 is present, the instruction FBLD (Load BCD) will load a BCD number that is stored in the 'packed decimal format'. The packed decimal format is an 80-bit ten-word (you never knew that ten-word data items were useful, did you?). Bit 79 is a sign bit which is set if the number is negative and clear if the number is positive. 18 digits are stored as 4-bit BCD numbers with the least-significant digit starting at bit zero of the ten-word. Bits 72 through 78 are unused: PACKED DECIMAL s --- D17 D16 .... D2 D1 digits 79 72 0 bit position The 80X87 can be used to perform math on these packed decimal BCD numbers. If desired, fractional numbers can be simulated by establishing a decimal point position and multiplying by a power of ten after the BCD is loaded into the 80X87. The instruction FBSTP will store the result from the 80X87 back into memory. The next part contains a procedure that will convert an ASCII string into a packed decimal BCD number: Comment* ASKY2BCD.ASM - ASCII to Binary Coded Decimal Written by Julian H. Schmidt, public domain code This routine will convert an ASCII string into a number stored in the packed decimal BCD numeric format. Creating packed BCD is more involved than creating unpacked BCD, so this will by my example. Acceptable characters in the ASCII string are the numbers (0 to 9). Any other characters will cause errors. This procedure does NO error checking! That is, it will not handle negative signs or radix points, or 'garbage' ASCII. This routine will convert up to 18 digits. Run this code from within DEBUG, and then (d)ump the Data Segment to verify that the Sample data (defined below, in the Data Segment) was converted and written to the Scratch buffer. On Entry: DS:DX Pointer to buffer of ASCII data that represents the 'number' to be converted to packed decimal. CX Number of characters in the string. On Exit: The Scratch data buffer will contain the packed decimal BCD. End of Comment* _data segment 'DATA' para Sample db '135792468098765432' ;18-character ASCII 'number' Scratch dt ? ;A ten-byte data item. _data ends _code segment 'CODE' byte assume cs:_code, ds:_data Driver proc mov ax,_data ;This is the program entry point. mov ds,ax ;Make the Data Segment accessible. mov dx,offset Sample ;Point DX to data to convert. mov cx,18 ;CX is number of chars to convert. call Asky2bcd ;Do the conversion. mov ax,4c00h ;Request function 4Ch (DOS). int 21h ;Dos's Terminate function. driver endp Asky2bcd proc push es ;Save some registers. ;------------------------------------------------ ; Initialize the scratch data buffer to zero | ;------------------------------------------------ mov di,offset Scratch ;DI points to the scratch buffer. mov ax,ds mov es,ax ;Now, ES = DS. push cx ;We can't destroy the contents of CX. mov cx,5 ;Initialize 5 words. xor ax,ax ;Word to fill buffer with = 00h. rep stosw ;Store the NULLs into Scratch buffer. pop cx ;---------------------------- ; Scan the ASCII digits | ;---------------------------- ;The Scratch buffer was initialized to all zeros. ;18 decimal places fit into 9 bytes in packed BCD. ;CX holds the number of digits left to process. ;DX points to the current position in the ASCII buffer. ;The Scratch buffer is 9 bytes long, access it with BX. add dx,cx dec dx ;This addition moves the pointer DX to the end of the ASCII ;string. I will scan from the end to the beginning. I do ;it this way because of the method by which I construct the ;packed BCD number. push ax ;AL will be used as a flag. push dx ;DX will also be used as a flag. mov bx,dx ;BX points to current ASCII character. xor ax,ax ;AL=0 means low nybble, 1 = high. mov dx,9 ;DX holds place in Scratch buffer. ;When a high nybble is processed, DX will be decremented ;to point to the 'next' byte. cmp cx,18 ;Is CX > 18 digits? jbe _ab0 ;If not, continue normally. mov cx,18 ;Truncate to 18 digits. _ab0: mov ah,byte ptr[bx] ;Copy the ASCII to ah. ;--------------- push cx ;Save the count temporarily. mov cx,4 ;Shift left by 4 places. shl ah,cl pop cx ;Restore the count. ;--------------- ;The nybble is now in the high four bits of AH. cmp al,0 ;Is the nybble flag zero? (low)? jne _ab1 ;If not, jump. ;The low nybble will now be taken care of. push bx ;I need a base register. mov bx,offset Scratch add bx,dx ;Move to current byte. ;--------------- push cx mov cx,4 shr ah,cl ;Shift right - creates BCD. pop cx ;Now, value is in LOW bits. ;--------------- mov [bx],ah ;Move the value into scratch buffer. pop bx ;All done! inc al ;Set the nybble flag. jmp short _ab2 ;Jump to the loop instruction(s). _ab1: ;High nybble taken care of here. push bx mov bx,offset Scratch add bx,dx or [bx],ah ;Move value into HIGH bits. pop bx xor al,al ;Clear the nybble flag. dec dx ;Move to previous byte in Scratch. _ab2: dec bx ;Go to next character. loop _ab0 ;Main scan loop. pop dx ;Registers pushed before the loop. pop ax pop es ret ;Return to the driver program. Asky2bcd endp _code Ends _stack Segment stack 'STACK' db 100 dup(0) _stack Ends END Driver