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

das

The das instruction is used to adjust the content of the AL register after that register is used to perform the subtraction 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 subtraction 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,73h        ;packed decimal "73"
   sub   al,45h        ;subtract packed decimal "45"
   daa                 ;AL = 2Eh -> 28h  (with CF clear = 28 packed decimal)

                            AL    CF      AF
; after subtraction        2Eh   clear    set
; daa 1st part (al-6)      28h   clear    set   (because al AND 0Fh > 9)
; daa 2nd part (nothing)   28h   clear    set   (al !> 99h && CF clear)
Example 2:
   mov   al,38h        ;packed decimal "38"
   sub   al,74h        ;subtract packed decimal "74"
   daa                 ;AL = 0C4h -> 64h  with borrow from next digit

                            AL     CF      AF
; after subtraction        0C4h    set   clear
; daa 1st part (nothing)   0C4h    set   clear  (al AND 0Fh !> 9 && AF clear)
; daa 2nd part (al-60h)     64h    set   clear  (because CF set)
Example 3:
   mov   al,47h        ;packed decimal "47"
   sub   al,49h        ;subtract packed decimal "49"
   daa                 ;AL = 0FEh -> 98h  with borrow from next digit

                            AL     CF      AF
; after subtraction        0FEh    set     set
; daa 1st part (al-6)      0F8h    set     set   (because al AND 0Fh > 9)
; daa 2nd part (al-60h)     98h    set     set   (because CF set)
The resulting Carry Flag must always be used with the next subtraction. There should never be any carry from the subtraction of the last packed byte; otherwise, the result would be erroneous. The smaller number must always be subtracted from the larger one and a negative sign inserted with the result if necessary.

The following code is an example of subtracting two large packed BCD numbers in "string" order, (i.e. num1 - num2). It assumes that neither number has leading 0 bytes reflected in its size. Otherwise, a longer number with such leading 0 bytes could actually be smaller than the other with fewer bytes and lead to an erroneous result.

Code is also added to convert the resulting packed BCD result to a null-terminated string without any leading "0" character. The byte count of each source number has been initialized in the data section to save coding instructions (the byte count could be obtained otherwise by several means).

.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    7h,27h,52h,83h,61h,84h,68h,15h ;727528361846815 packed
   signbit  db    ?                              ;0=positive, !0=negative
   txtbuf   db    32 dup(?)
      
.code

start:
   mov   edi,offset num1
   mov   esi,offset num2
   mov   ecx,num1size
   mov   edx,num2size
   mov   signbit,0

   .if   ecx < edx      ;result would be negative
negative:
      xchg  esi,edi     ;use EDI to point to longest number
      xchg  ecx,edx     ;use ECX to hold largest count of packed bytes
      mov   signbit,1
   .elseif ecx == edx
      push  esi
      push  edi
      push  ecx
      repz  cmpsb       ;compare the two numbers
      pop   ecx
      pop   edi
      pop   esi
      .if   ZERO?       ;both numbers are equal
         mov   answer,0
         mov   txtbuf,"0"
         jmp   exit
      .endif
      jnc   negative    ;num2 > num1
   .endif

   add   edi,ecx        ;must start with least significant digits
   add   esi,edx        ;   idem

   mov   ebx,offset answer
   clc                  ;start with CF clear
@@:
   dec   edi
   dec   esi
   mov   al,[edi]
   sbb   al,[esi]
   das                  ;decimal adjust subtraction result
   mov   [ebx],al       ;store it
   inc   ebx
   dec   ecx
   dec   edx
   jnz   @B             ;process all bytes of smallest number

   pushfd               ;keep current flags
   or    ecx,ecx        ;is longest number also completed?
   jz    no_more

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

no_more:
   mov   ecx,ebx        ;EBX points to byte following most significant one
   mov   edx,offset answer
   sub   ecx,edx        ;ECX = number of packed bytes in answer
   mov   edi,offset txtbuf

; The following is to convert the result to ASCII
; First insert negative sign if necessary

   .if   signbit != 0
      mov   al,"-"
      stosb
   .endif

; Remove any leading packed BCD 0's

@@:
   dec   ebx
   mov   al,[ebx]
   or    al,al
   jnz   @F
   dec   ecx            ;adjust count of bytes in answer
   jmp   @B             ;check next byte

; Then process separately a leading 0 in the most significant byte

@@:
   .if   al < 10        ;no high nibble
      add   al,30h
      stosb
      jmp   nextbyte
   .endif

; Process the other bytes

@@:
   movzx eax,byte ptr[ebx]
   ror   ax,4           ;shift most significant nibble to least significant
                        ;while least significant transfers to AH
   ror   ah,4           ;do same with AH which contains the least
                        ;significant nibble of AL in the upper 4 bits
   add   ax,3030h       ;convert both digits to ASCII
   stosw                ;store both characters
              ;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
exit:
   mov   [edi],cl       ;add string terminating 0

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