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

daa

The daa instruction is used to adjust the content of the AL register after that register is used to perform the addition of two packed BCDs. The CPU uses the following logic:

   CF_old = CF
IF (al AND 0Fh > 9) or (the Auxilliary Flag is set)
   al = al+6
   CF = CF or CF_old
   AF set
ENDIF
IF (al > 99h) or (Carry Flag is set)
   al = al + 60h
   CF set
ENDIF
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 or CF flags (such as a mov instruction).
Example 1:
   mov   al,38h        ;packed decimal "38"
   add   al,45h        ;add packed decimal "45"
   daa                 ;AL = 7Dh -> 83h  (with CF clear = 83 packed decimal)

                            AL    CF      AF
; after addition           7Dh   clear   clear
; daa 1st part (al+6)      83h   clear    set   (because al AND 0Fh > 9)
; daa 2nd part (nothing)   83h   clear    set   (al !> 99h && CF clear)
Example 2:
   mov   al,88h        ;packed decimal "88"
   add   al,74h        ;add packed decimal "74"
   daa                 ;AL = 0FCh -> 62h  (with CF set = 162 packed decimal)

                            AL     CF      AF
; after addition           0FCh   clear   clear
; daa 1st part (al+6)       02h    set     set   (because al AND 0Fh > 9)
; daa 2nd part (al+60h)     62h    set     set   (because CF set)
Example 3:
   mov   al,47h        ;packed decimal "47"
   add   al,69h        ;add packed decimal "69"
   daa                 ;AL = 0B0h -> 16h  (with CF set = 116 packed decimal)

                            AL     CF      AF
; after addition           0B0h   clear    set
; daa 1st part (al+6)      0B6h   clear    set   (because AF set)
; daa 2nd part (al+60h)     16h    set     set   (because AL > 99h)
The resulting Carry Flag must always be used with the next addition, or inserted as an additional digit following the addition of the last packed digits.

The following code is an example of adding two large packed BCD numbers in "string" order. Code is also added to convert the resulting packed BCD sum to a null-terminated string without any leading "0" character.

.data
   answer   db    16 dup(0)
   num1size dd    7
   num2size dd    8
   num1     db    78h,96h,19h,03h,21h,38h,55h     ; 78961903213855 packed
   num2     db    07h,27h,52h,83h,61h,84h,68h,15h ;727528361846815 packed
   txtbuf   db    32 dup(0)
      
.code

start:
   mov   ebx,offset answer
   mov   edi,offset num1
   mov   esi,offset num2
   mov   ecx,num1size
   mov   edx,num2size
   add   edi,ecx        ;must start with least significant digits
   add   esi,edx        ;   idem
   .if   ecx < edx
      xchg  esi,edi     ;use EDI to point to longest number
      xchg  ecx,edx     ;use ECX to hold largest count of packed bytes
   .endif
   clc                  ;start with CF clear
@@:
   dec   edi
   dec   esi
   mov   al,[edi]
   adc   al,[esi]
   daa                  ;decimal adjust addition result
   mov   [ebx],al       ;store it
   inc   ebx
   dec   ecx
   dec   edx
   jnz   @B             ;process all bytes of smallest number

   pushfd               ;keep current flags
                        ;the next instruction would modify them
   or    ecx,ecx        ;is longest number also completed?
   jz    lastcarry

   popfd                ;retrieve flags
@@:
   dec   edi
   mov   al,[edi]
   adc   al,0
   daa
   mov   [ebx],al
   inc   ebx
   dec   ecx
   jnz   @B             ;process remaining bytes of longest number
   pushfd

lastcarry:
   popfd                ;retrieve the last flags for the CF
   jnc   @F             ;skip if no last Carry
   mov   byte ptr[ebx],1
   inc   ebx            ;EBX now points to byte after most significant digit

@@:
   mov   ecx,ebx
   mov   edx,offset answer
   sub   ecx,edx        ;ECX = number of packed bytes in answer
   mov   edi,offset txtbuf
   dec   ebx

; The next few instructions are for processing the very first byte of the
; packed BCD if the high nibble is 0, and thus prevent a leading 0
; in the converted string.

   mov   al,[ebx]
   .if   al < 10        ;if high nibble = 0
      add   al,30h      ;convert single digit to ASCII
      stosb             ;store only 1 digit
      jmp   nextbyte
   .endif

@@:
   movzx eax,byte ptr[ebx]
   ror   ax,4           ;shift most significant nibble to least significant
                        ;while least significant nibble transfers to AH
   ror   ah,4           ;do same now with AH which contained
                        ;the least significant nibble of AL in upper 4 bits
   add   ax,3030h       ;convert both digits to ASCII
   stosw                ;store both digits
              ;the more significant of the two having been kept in AL will
              ;get stored ahead of the less significant one in the string
nextbyte:
   dec   ebx
   dec   ecx
   jnz   @B
   mov   [edi],cl

; The ASCII representation of the packed BCD sum is now available as a
; null-terminated string at the txtbuf address.