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

aas

The aas instruction is used to adjust the content of the AL register after that register has been used to perform the subtraction 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
ENDIF
al = al AND 0Fh

(The Auxilliary Flag is set whenever there is a carry of the lower 4 bits from a subtraction.)

Although this instruction should be used immediately after the subtraction 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
   sub  al,2    ;al = 5, AF clear, CF clear
   aas          ;al = 5, ah is unchanged, CF clear

Example2:
   mov  al,3
   sub  al,6    ;al = -3 = 0FDh, al AND 0Fh = 0Dh > 9, AF set, CF set
   aas          ;al = al-6 = -9 = 0F7h AND 0Fh = 7, ah=ah-1, CF set

Example3:
   mov  al,2
   sub  al,9    ;al = -7 = 0F9h, al AND 0Fh = 9 <= 9, AF set, CF set
   aas          ;al = al-6 = -13 = 0F3h AND 0Fh = 3, ah=ah-1, CF set
Note: Because only the lower 4 bits of AL are retained, it is thus possible to subtract the ASCII values of numerical digits directly without the need to convert them to their binary values beforehand.

Example4:
   mov  al,"7"  ;37h
   sub  al,"9"  ;al = 37h-39h = 0FEh AND 0Fh = 0Eh > 9, AF set, CF set
   aas          ;al = 0FEh-6 = 0F8h AND 0Fh = 8, ah = ah-1, CF set

The subtraction of two large numbers requires more preparation than the simple addition of similar numbers. The smaller of the two numbers must always be subtracted from the larger one to get a correct answer. When the larger one has to be subtracted from the smaller one, a "-" sign must then be inserted in front of the answer.

Furthermore, the sign of each number should be checked as a first step. For instance, the subtraction of a negative number from a positive one should be handled by addition of the digits.

The simple subtraction of two numbers is still relatively straightforward. Repeated subtractions is also one of the few ways that large numbers can be divided.

The next example will show how such division can be coded. The two large ASCII numbers (num1/num2) are assumed to be valid integers (numerical digits only) and their digits in the "string" order. They will already have been placed in memory buffers of adequate size. Their individual size will also be already in their respective memory variable. The buffer of the numerator will be used to store the result of the subtractions since that result must be used repeatedly. A third buffer will be used to store the answer of the division also in the "string" order.

The code also shows how a required number of fractional digits can be included in the answer.


.data
   num1add dd   ?
   num2add dd   ?
   size1   dd   21
   size2   dd   11
   decimal dd   5      ;number of decimal places required in answer
   prenum1 db   0      ;to avoid extra code
   num1    db   "491756380472816275825",11 dup(0)
   prenum2 db   0      ;to avoid extra code
   num2    db   "19932850157",13 dup(0)
   answer  db   32 dup(?)

.code

; EDX will be used to hold the count of repeating cycles
; ECX will be used for various counters
; ESI will be used to point to the current digit of the numerator string
; EDI will be used to point to the current digit of the denominator string
; EBX will be used to point to the current digit of the answer

; Both ASCII numbers first have to be converted to binary for this operation

; Convert numerator
   mov   edi,offset num1
   mov   ecx,size1
@@:
   mov   al,[edi]
   and   al,0Fh
   stosb
   dec   ecx
   jnz   @B

; If some decimal places must also be computed for the answer, an equivalent
; number of trailing 0's must be appended to the numerator.

   xor   eax,eax
   mov   ecx,decimal
   rep   stosb

; Convert denominator
   mov   edi,offset num2
   mov   ecx,size2
@@:
   mov   al,[edi]
   and   al,0Fh
   stosb
   dec   ecx
   jnz   @B

; Leading 0's in the numerator are immaterial. However, extra leading 0's
; in the denominator must be removed and its size adjusted accordingly

   mov   edi,offset num2
   xor   eax,eax
   mov   ecx,size2
   repz  scasb              ;find first non-zero byte
   inc   ecx                ;correction for first non-zero byte
   mov   size2,ecx
   dec   edi                ;back to first non-zero digit
   dec   edi                ;will allow one necessary leading 0
   mov   num2add,edi        ;address of first working byte

   mov   esi,offset prenum1
   mov   num1add,esi        ;to keep track of the position of the most 
                            ;significant digit
   mov   ebx,offset answer

; The following will essentially define the number of digits in the
; integer portion of the answer

   mov   edx,size1
   sub   edx,size2
   jnc   @F

zero_integer:
   mov   byte ptr[ebx],"0"  ;denominator would be larger than numerator
   inc   ebx
   jmp   do_decimals

@@:
   inc   edx

; The following code will avoid leading 0's in the answer
; It will also detect a smaller numerator due to leading 0's

@@:
   mov   esi,num1add
   mov   edi,num2add
   mov   ecx,size2
   inc   ecx
   repz  cmpsb
   jnc   outerloop
   inc   num1add
   dec   edx
   jz    zero_integer
   jmp   @B

outerloop:
   mov   byte ptr[ebx],"0"  ;initialize each byte to ASCII 0
                            ;this should only be done if the answer
                            ;is to be displayed immediately
innerloop:

;check if denominator can be subtracted from numerator
   mov   esi,num1add
   mov   edi,num2add
   mov   ecx,size2
   inc   ecx
   repz  cmpsb
   jc    num2larger

   mov   ecx,size2
   mov   esi,num1add
   add   esi,ecx            ;points to least significant digit
                            ;for subtracting denominator
   mov   edi,num2add
   add   edi,ecx            ;points to end of denominator
   inc   ecx                ;may need to subtract one more digit
   clc                      ;clear CF to start subtraction

@@:
   mov   al,[esi]
   sbb   al,[edi]
   aas
   mov   [esi],al
   dec   esi
   dec   edi
   dec   ecx
   jnz   @B

   inc   byte ptr[ebx]      ;increment digit of answer
   jmp   innerloop          ;repeat subtraction

num2larger:
   inc   num1add
   inc   ebx
   dec   edx
   jnz   outerloop

; At this point, the integer portion of the answer is ready for display.
; If a number of decimal places would be required as specified by the 
; decimal variable, those could continue to be computed with the
; following code.

do_decimals:
   mov   edx,decimal
   or    edx,edx
   jz    exit
   mov   decimal,0          ;to skip the second time around
   mov   byte ptr[ebx],"."  ;insert decimal delimiter
   inc   ebx
   jmp   outerloop

exit:
   mov   byte ptr[edi],0

; The answer is now ready for display as a null-terminated string

The above code provides a truncated answer. Additional instructions could be added to provide a rounded answer.

Additional instructions could also be added to cater for input containing decimal fractions. The possibilities are only left to the imagination of the programmer.