; PRDEC32.ASM  --  Include-able library function file

; Mach32_DecASC:  32-bit unsigned machine integer to ASCII decimal conversion

; K. Heidenstrom  (kheidens@actrix.gen.nz)

; Modified:
;
; KH.940729.001  Started
; KH.940730.002  First fully-defined working version, renamed all labels
; KH.941114.003  Version for posting on the net in comp.lang.asm.x86

; Notes
;
; This function converts a 32-bit unsigned machine longword in DX|AX to ASCII
; decimal representation.  It is similar to the itoa() function in C.
;
; Output is controlled by two parameters specified in CX on entry.
;
; CL contains the number of digits desired in the output.  This may be any
; value, from 0 to 255, but is normally in the range 1 to 10.
;
; CH contains the padder character for right-justified numbers.  If this
; register is set to zero, no padding is used, and only the digits necessary
; to represent the number are output.  If CH is not zero, then it is taken
; as an ASCII character to be used to pad the number on the left, to the width
; specified in CL.  In this case, a space or a "0" are normally used in CH.
;
; If the number of digits specified in CL is insufficient to contain the value
; represented by DX|AX, then all columns are output as "#" characters (this is
; controlled by the PrDecOvrflowChr equate below).  In this case, the routine
; will return with carry set.  It will also return with carry set if CL was
; zero (i.e. no digits at all).
;
; The subroutine's stack space requirement is sixteen bytes, or twelve bytes
; plus the stack space required by the output function, whichever is greater.
;
; The output method must be specified at assembly time, by defining a
; PrDec_Output macro.  This macro will be assembled into the part of the
; subroutine where the character output is required.  The requirements for
; the character output macro are as follows:
;
; On entry, AL contains the character to be displayed or output.
;
; AX DX BP DI	These MAY be modified by the macro code.
; BX CX SI	These MUST NOT be modified by the macro code.
; DI DS ES	These are not used at all by the Mach32_DecASC subroutine.
;
; This last group of registers are not used or changed by the subroutine code,
; and may be set up before entering the subroutine, then used by the output
; macro, and will be returned by the subroutine.  This is intended to support
; use of STOSB as the output function macro.  DI can be set up before entry to
; the subroutine, and the output function macro can contain a STOSB, and on
; return from the subroutine, DI will point past the last stored character.
; Note that the subroutine does not use, or modify, the direction flag.
;
; The output function macro is executed in left-to-right sequence as the
; digits of the output value are calculated.
;
; The total length of the PrDec_Output macro must be limited to 33 bytes (at
; the moment - this may change), in order to avoid a bad branch error at the
; end of the subroutine.
;
; Example PrDec_Output macros:
;
; MACRO   PrDec_Output
;		  stosb			; Add digit to string being generated
; ENDM
;
; MACRO   PrDec_Output
;		  push	  bx		; Preserve
;		  xor	  bx,bx		; Output to first display page
;		  mov	  ah,0Eh	; TTY output
;		  int	  10h		; Output to BIOS
;		  pop	  bx		; Restore
; ENDM
;
; MACRO   PrDec_Output
;		  mov	  dl,al		; Digit to DL
;		  mov	  ah,2		; Char output
;		  int	  21h		; Output to DOS
; ENDM


PrDecOvrflowChr	=	"#"		; Char to display on overflow

PROC	Mach32_DecASC	near
;				Func:	Convert unsigned long machine integer
;					to ASCII decimal representation
;				In:	DX|AX = Value to be converted
;					CL = Number of digits to display
;					     (normally in range 1 - 10 but all
;					     values are handled correctly)
;					CH = Padder (typically " " or "0", or
;					     0 for no leading padding)
;				Out:	Characters are output according to the
;					PrDec_Output macro which must be defined
;					before this file is included.
;					CF = Overflow status:
;						NC = Alright, no overflow
;						CY = Overflow occurred
;				Lost:	Flags (DF is not affected)
;					DI, DS, and ES may be modified by the
;					output function macro.
;				Note:	Input value is treated as an unsigned
;					longword, in the range 0 - 4294967295.
;				Note:	If the value won't fit into the number
;					of digits specified, all digits are
;					output as '#' symbols, indicating
;					overflow, and carry is set on return.

		push	si
		push	bp
		push	dx
		push	cx
		push	bx
		push	ax		; Preserve registers

		test	cl,cl
		jz	PrDec_Error	; If no digits specified
		xchg	ax,bx		; Loword to BX
		mov	si,dx		; Hiword to SI

; Check digit position is within range

PrDec_DigitLp:	cmp	cl,11		; Check that we're at a valid digit
		jae	PrDec_DoPad	; If not yet, just do space padding

		mov	dx,1		; Init loword of divisor to 1
		sub	cl,dl		; Get digit position
		jb	PrDec_Exit	; If no digits, or just done last digit

; Calculate divisor in BP|DX as 10 ^ digit position number

		push	cx		; Keep misc info in CL and CH
		xor	bp,bp		; Hiword of divisor to zero
PrDec_DivLoop:	sub	cl,1		; Count down the shifts
		jb	PrDec_GotDiv	; If done, have divisor
		push	bp		; Push hiword
		mov	ax,dx		; Keep loword
		shl	dx,1		; Multiply by two
		rcl	bp,1		; Carry into hiword
		shl	dx,1		; Multiply by two again
		rcl	bp,1		; Carry into hiword
		add	dx,ax		; Now add original value to value x 4
		pop	ax		; Restore old hiword
		adc	bp,ax		; Add hiwords too
		shl	dx,1		; Now have value x 5, double it again
		rcl	bp,1		; And hiword
		jmp	SHORT PrDec_DivLoop ; Continue
PrDec_GotDiv:	pop	cx		; Have divisor, restore CX

; Calculate digit by repeatedly dividing value in SI|BX by divisor in BP|DX

		xor	ax,ax		; Count of divides
PrDec_CalcLoop:	inc	ax		; Increment counter
		cmp	al,10		; Make sure digit is within range
		ja	PrDec_Overflow	; If not, display overflow indication
		sub	bx,dx		; Subtract loword of divisor from value
		sbb	si,bp		; Borrow from hiword
		jnb	PrDec_CalcLoop	; Borrowed below zero yet?  If not, loop
		add	bx,dx		; Restore remainder loword
		adc	si,bp		; Restore hiword too
		add	al,"0"-1	; Calculate digit value in ASCII

; Check value of digit

		cmp	al,"0"		; Is digit zero?
		jne	PrDec_SetZero	; If not
		test	cl,cl		; Final position?
		jz	PrDec_GotDigit	; Force printing final zero
PrDec_DoPad:	mov	al,ch		; If digit is zero, substitute padder
		jmp	SHORT PrDec_GotDigit ; Continue
PrDec_Overflow:	mov	si,-1		; Force overflow indication from now on
		mov	al,PrDecOvrflowChr ; Indicate overflow
PrDec_SetZero:	mov	ch,"0"		; Had non-zero - no more padding now!
PrDec_GotDigit:	test	al,al
		jz	PrDec_DigitLp	; If zero, and no padding requested

; Output character in AL using appropriate output mode

		PrDec_Output

		jmp	SHORT PrDec_DigitLp ; Loop for more digits

PrDec_Exit:	test	si,si
		jz	PrDec_Exit2	; If didn't have overflow, exit no carry
PrDec_Error:	stc			; If overflow occurred, exit carry set
PrDec_Exit2:	pop	ax
		pop	bx
		pop	cx
		pop	dx
		pop	bp
		pop	si		; Restore registers
		ret
ENDP	Mach32_DecASC