; Title GCD.asm
; Author: A.Pinck; modified by Sv. Ranev
; Date written: 3 November 1997, 20 March 2007
; Purpose:
;  Enter two numbers (unsigned, maximum of 4 decimal digits)
;  Calculate and display their Greatest Common Divisor
;
; -Ian! D. Allen - idallen@idallen.ca - www.idallen.com
;
; This is .COM type program. 
; Both the Code Segment (CS) and Data Segment (DS) are the same.
GCD      segment
         assume   cs:GCD,ds:GCD
         org   100h

start:   mov   dx,offset askFor1st
         mov   ah,09
         int   21h     ;prompt for first number

         call  getNum  ;value returned in AX
         jc    inDataErr ;Carry flag set if input invalid
         cmp   ax,0
         je    inDataZero
         mov   large,ax   ;save value if OK

         mov   dx,offset askFor2nd
         mov   ah,09
         int   21h     ;prompt for second number

         call  getNum
         jc    inDataErr
         cmp   ax,0
         je    inDataZero
         mov   small,ax

loop1:   mov   ax,large    ;Loop until large and small are equal
         cmp   ax,small
         jz    done

         jnc   reduce      ;if large > small don't switch
         mov   bx,small    ;otherwise, switch large and small values
         mov   small,ax
         mov   large,bx

reduce:  mov   ax,large  ;reduce large by small amount
         sub   ax,small
         mov   large,ax

         jmp   loop1    ;loop back

done:    call  showNum
         mov   al,00       ;no error indicator
         jmp   exit

;--- error message handling ---
inDataErr:
         mov   dx,offset dataErr
         mov   al,04       ;invalid data error code
         jmp   showErr
inDataZero:
         mov   dx,offset dataZero
         mov   al,02       ;zero data value code
showErr:
         mov   ah,09
         int   21h         ;issue error message
;-------------------------------
exit:
         mov   ah,4Ch ;terminate with code
         int   21h

;-- Data area --------------------------------------
large       dw ?
small       dw ?
askFor1st   db 0Dh,0Ah,"Enter first number: $"
askFor2nd   db 0Dh,0Ah,"Enter second number: $"
dataErr     db 0Dh,0Ah,"Invalid/Non-numeric character in input.$"
dataZero    db 0Dh,0Ah,"Zero input data value is not legal.$"

;==========================================

; showNum displays the 2's complement number
;  in AX as a decimal number

showNum  proc  near
         push  ax      ;save working registers
         push  bx
         push  cx
         push  dx

         push  ax
         mov   dx,offset newline ;ensure display is on a newline
         mov   ah,09
         int   21h
         pop   ax

         cmp   ax,0    ;test for negative
         jge   positive;skip ahead if positive
         mov   ah,02   ;display character code
         mov   dl,'-'  ;  minus sign
         int   21h
         mov   bx,ax
         sub   ax,bx   ;ax now zero
         sub   ax,bx   ;ax now positive

positive:        
         mov   bx,10   ;divide by 10 to convert to decimal
         mov   cl,0    ;count of number of digits produced
div_loop:
         mov   dx,0    ;setup to divide dx|ax by bx
         idiv  bx      ;unsigned division: 
                       ;quotient in ax, remainder in dx
         push  dx      ;save the digit (remainder)
         inc   cl      ;keep track of number of digits
         cmp   ax,0    ;until nothing left to divide
         jnz   div_loop;  loop back

show_digit:
         pop   dx
         add   dl,30h  ;convert digit to ASCII code
         mov   ah,02   ;display character
         int   21h
         dec   cl      ;loop until all digits shown
         jnz   show_digit

         pop   dx      ;restore working registers
         pop   cx
         pop   bx
         pop   ax
         ret

;-- local data --
newline  db 0Dh,0Ah,"$"
showNum  endp

;==========================================

; getNum returns number input (converted to
;   binary) in ax
; input is assumed to be a positive, decimal
;   value with a maximum of 4 digits, terminated
;   with a carriage return
; Carry flag is set (on) if an input error is
;   detected (Carry flag is clear (off) if the
;   input is valid

getNum   proc  near
         push  bx      ;save working registers
         push  cx
         push  dx

         mov   num,0
         mov   bh,'9'  ;setup for valid range testing
         mov   bl,'0'
         mov   cl,4    ;maximum number of digits acceptable

get_loop:
         mov   ah,01   ;get key (with echo)
         int   21h
         cmp   al,0Dh  ;exit loop on carriage return
         je    get_exit

         cmp   bh,al   ;check that it's < '9'
         jc    bad_digit ;error if its not
         sub   al,bl
         jc    bad_digit ;error if its < '0'

         mov   ah,0
         push  ax
         mov   ax,num
         mov   dx,10   ;decimal based system
         imul  dx      ;multiply ax by dx results in dx|ax
         mov   num,ax  ;value of prior digits times 10
         pop   ax      ;add this digit's value
         add   num,ax

         sub   cl,1
         jnc   get_loop
bad_digit:  ;-- if we get here something is wrong
            ;-- by careful coding we have ensured that
            ;-- the carry flag will be set
            ;-- this is the "error" return indicator
get_exit:
         mov   ax,num
         pop   dx      ;restore working registers
         pop   cx
         pop   bx
         ret

;--- local data ---
num      dw ?

getNum   endp
;==========================================

GCD      ends
         end   start

