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

