BINARY   CODED   DECIMALS
by Raymond Filiatreault
Copyright 2005
Latest revision April 2005

aaa

The aaa instruction is used to adjust the content of the AL register after that register has been used to perform the addition of two unpacked BCDs. The CPU uses the following logic:

IF (al AND 0Fh > 9) or (the Auxilliary Flag is set)
   al = al+6
   ah = ah+1
   CF set
   AF set
ENDIF
al = al AND 0Fh
(The Auxilliary Flag is set whenever there is an overflow of the lower 4 bits after an addition.)

Although this instruction should be used immediately after the addition instruction, it could be used later as long as no other intervening instruction would have changed the AF flag (such as a mov instruction).

Example1:
   mov  al,7
   add  al,2    ;al = 9, AF clear, CF clear
   aaa          ;al = 9, ah is unchanged, CF clear
Example2:
   mov  al,7
   add  al,6    ;al = 13 = 0Dh, al AND 0Fh = 0Dh > 9, AF clear, CF clear
   aaa          ;al = al+6 = 19 = 13h AND 0Fh = 3, ah=ah+1, CF set
Example3:
   mov  al,7
   add  al,9    ;al = 16 = 10h, al AND 0Fh = 0 <= 9, AF set, CF clear
   aaa          ;al = al+6 = 22 = 16h AND 0Fh = 6, ah=ah+1, CF set
Note: Because only the lower 4 bits of AL are retained, it is thus possible to add the ASCII values of numerical digits directly without the need to convert them to their binary values beforehand.
Example4:
   mov  al,"7"  ;37h
   add  al,"2"  ;al = 37h+32h = 69h AND 0Fh = 9, AF clear, CF clear
   aaa          ;al = 69h AND 0Fh = 9, ah is unchanged, CF clear

The next example will consist in using the above information to add two large ASCII numbers. They will already have been placed in 32-byte memory buffers. Their individual size will also be already in their relative memory variable. A 3rd buffer will be used to store the answer.


.data
   num1    db   "491756380472816275825",11 dup(0)
   num2    db   "8387562019932850157",13 dup(0)
   size1   dd   21
   size2   dd   19
   answer  db   32 dup(0)

.code

; ECX will be used to hold the count of digits in the longer string
; EDX will be used to hold the count of digits in the shorter string
; ESI will be used to point to the current digit of the longer string
; EDI will be used to point to the current digit of the shorter string
; EBX will be used to point to the current digit of the answer

   mov   eax,size1
   .if   eax >= size2
      mov   ecx,size1
      mov   edx,size2
      mov   esi,offset num1
      add   esi,ecx          ;points to the byte after the last digit
      dec   esi              ;now points to last digit
      mov   edi,offset num2
      add   edi,edx          ;points to the byte after the last digit
      dec   edi              ;now points to last digit
   .else
      mov   ecx,size2
      mov   edx,size1
      mov   esi,offset num2
      add   esi,ecx          ;points to the byte after the last digit
      dec   esi              ;now points to last digit
      mov   edi,offset num1
      add   edi,edx          ;points to the byte after the last digit
      dec   edi              ;now points to last digit
   .endif

; The above could also include code to allocate a memory block of proper
; size for the answer and then load EBX with its address.

   mov   ebx,offset answer   ;the answer will be stored in reverse order
   clc                       ;clear the CF before starting the addition

; The inc and dec instructions don't affect the CF.
; The CF would be affected if you change those for add reg,1 and sub reg,1
; and you would then need to save and restore that flag within the loops.

shortloop:
   mov   al,[esi]
   adc   al,[edi]            ;add the two digits and
                             ;include CF from previous addition
   aaa                       ;adjust the resulting digit of the addition
   mov   [ebx],al            ;store it
   inc   ebx
   dec   esi
   dec   edi
   dec   ecx
   dec   edx
   jnz   shortloop            ;continue until short number completed

; At this point, all the digits of the shorter number have been added.
; The remaining digits of the longer number must still be processed
; with any carry from previous additions.

   inc   ecx
   dec   ecx                  ;check if long number also terminated
   jnz   longloop
   jnc   finish               ;no overflow from last addition
   mov   byte ptr[ebx],1      ;last addition would have overflow
   inc   ebx
   jmp   finish

longloop:
   mov   al,[esi]
   adc   al,0
   aaa
   mov   [ebx],al
   inc   ebx
   dec   esi
   dec   ecx
   jnz   longloop

   jnc   finish               ;no overflow from last addition
   mov   byte ptr[ebx],1
   inc   ebx

finish:

; At this point the answer is in reverse order and in binary format.
; The digits will now be reversed and converted to a null-terminated
; ASCII string ready for display.
; ESI will be used to point from the start of the answer
; EBX will be used to point from the end of the answer

   mov   byte ptr[ebx],0       ;to terminate ASCII string
   mov   esi,offset answer
   dec   ebx                   ;point to last digit

@@:
   mov   al,[esi]
   mov   ah,[ebx]
   add   ax,3030h              ;convert both to ASCII
   mov   [esi],ah
   mov   [ebx],al              ;switch the two digits
   inc   esi
   dec   ebx
   cmp	 esi,ebx
   jbe   @B

; The answer is now ready for display

There could be many variations to the above code.

For instance, you could add code to shift the shorter number in its buffer and pad it in front with 0's to have the same length for both numbers; the code for the "longloop" could then be eliminated.

Another option to improve execution speed when one number is much shorter than the other one could be to exit the "longloop" as soon as there is no carry and then simply copy the remaining digits of the longer number to the answer.

And, if one of the numbers being added is not to be used again, its buffer could be used to store the answer of the addition. Be sure, however, that its buffer size is large enough for the answer and that you don't overwrite the original digits before they are used.