Task

Write a subroutine, function, whatever it may be called in your language, that takes an integer number and returns an English text sequence starting with the English cardinal representation of that integer, the word 'is' and then the English cardinal representation of the count of characters that made up the first word, followed by a comma.

Continue the sequence by using the previous count word as the first word of the next phrase, append 'is' and the cardinal count of the letters in that word.

Continue until you reach four. Since four has four characters, finish by adding the words 'four is magic' and a period. All integers will eventually wind up at four.

For instance, suppose your are given the integer 3. Convert 3 to Three, add  is , then the cardinal character count of three, or five, with a comma to separate if from the next phrase. Continue the sequence five is four, (five has four letters), and finally, four is magic.

     Three is five, five is four, four is magic.

For reference, here are outputs for 0 through 9.

     Zero is four, four is magic.
     One is three, three is five, five is four, four is magic.
     Two is three, three is five, five is four, four is magic.
     Three is five, five is four, four is magic.
     Four is magic.
     Five is four, four is magic.
     Six is three, three is five, five is four, four is magic.
     Seven is five, five is four, four is magic.
     Eight is five, five is four, four is magic.
     Nine is four, four is magic.



Some task guidelines

 You may assume the input will only contain integer numbers.
 Cardinal numbers between 20 and 100 may use either hyphens or spaces as word separators but they must use a word separator. (23 is twenty three or twenty-three not twentythree.)
 Cardinal number conversions should follow the  English short scale. (billion is 1e9, trillion is 1e12, etc.)
 Cardinal numbers should not include commas. (20140 is twenty thousand one hundred forty not twenty thousand, one hundred forty.)
 When converted to a string, 100 should be one hundred, not a hundred or hundred, 1000 should be one thousand, not a thousand or thousand.
 When converted to a string, there should be no and in the cardinal string. 130 should be one hundred thirty not one hundred and thirty.
 When counting characters, count all of the characters in the cardinal number including spaces and hyphens. One hundred fifty-one should be 21 not 18.
 The output should follow the format "N is K, K is M, M is ... four is magic." (unless the input is 4, in which case the output should simply be "four is magic.")
 The output can either be the return value from the function, or be displayed from within the function.
 You are encouraged, though not mandated to use proper sentence capitalization.
 You may optionally support negative numbers. -7 is negative seven.
 Show the output here for a small representative sample of values, at least 5 but no more than 25. You are free to choose which which numbers to use for output demonstration.


You can choose to use a library, (module, external routine, whatever) to do the cardinal conversions as long as the code is easily and freely available to the public.

If you roll your own, make the routine accept at minimum any integer from 0 up to 999999. If you use a pre-made library, support at least up to unsigned 64 bit integers. (or the largest integer supported in your language if it is less.)

Four is magic is a popular code-golf task. This is not code golf. Write legible, idiomatic and well formatted code.




Related tasks

   Four is the number of_letters in the ...
   Look-and-say sequence
   Number names
   Self-describing numbers
   Summarize and say sequence
   Spelling of ordinal numbers
   De Bruijn sequences


puts:		equ	9h	; MS-DOS syscall to print a string
		cpu	8086
		bits	16
		org	100h
section		.text
		;;;	Read number from the MS-DOS command line
		;;;	The task says numbers up to 999999 need to be 
		;;;	supported, so we can't get away with using MUL.
		mov	cl,[80h]	; Is there an argument?
		test	cl,cl
		jnz	havearg
		mov	ah,puts		; If not, print "no input"
		mov	dx,errinput
		int	21h
		ret			; And stop.
havearg:	mov	si,82h		; Start of argument string
		xor	ch,ch		; CX = argument length
		dec	cx		; Minus one (space before argument)
		xor	ax,ax		; Accumulator starts out at 0
		xor	dx,dx
numloop:	mov	bp,ax		; DX:AX *= 10
		mov	di,dx
		add	ax,ax		; ... *2 
		adc	dx,dx		
		add	ax,ax		; ... *4
		adc	dx,dx
		add	ax,bp		; ... *5
		adc	dx,di
		add	ax,ax		; ... *10
		adc	dx,dx
		mov	bx,ax
		lodsb			; Get digit
		sub	al,'0'
		xor	ah,ah
		add	ax,bx		; Add digit
		adc	dx,0
		loop	numloop		; Next digit if there is one
		;;;	DX:AX now contains the binary representation of
		;;;	the decimal input.
		cmp	dx,0Fh		; Check that DX:AX <= 999999
		jb	donum
		cmp	ax,4240h	; 0F4240h = 1000000
		jb	donum
		mov	ah,puts		; Otherwise, print error message
		mov	dx,errhigh
		int	21h
		ret
		;;;	DX:AX = current number
donum:		push	dx		; Keep number
		push	ax 
		mov	di,numstring	; Create the string for the number
		call	cardinal
		mov	[di],byte '$'
		xor	[numstring],byte 32	; Capitalize first letter
.print:		mov	dx,numstring	; Print the string
		mov	ah,puts
		int	21h
		mov	dx,is		; print ' is ',
		int	21h
		pop	ax		; Retrieve number
		pop	dx
		test	dx,dx		; DX:AX = 4 = magic
		jnz	.nomagic	; DX <> 0 = not magic
		cmp	ax,4		; If AX=4 then magic
		je	.magic
.nomagic:	sub	di,numstring	; Calculate length of string
		xor	dx,dx		; Set DX:AX to DI
		mov	ax,di
		push	dx		; Store new number on stack
		push	ax
		mov	di,numstring	; Make string for new number
		call	cardinal
		mov	[di],byte '$'
		mov	dx,numstring	; Print the string
		mov	ah,puts
		int	21h
		mov	dx,commaspace	; Print comma and space
		int	21h
		jmp 	.print		; Then use next number as input
.magic:		mov	dx,magic 	; print "magic.",
		mov	ah,puts
		int	21h
		ret			; and stop
		;;;	Subroutine: assuming 0 <= DX:AX <= 999999, write
		;;;	cardinal representation at ES:DI.
cardinal:	mov	bp,ax
		or	bp,dx
		jz	.zero		; If it is zero, return 'Zero'
		mov	bp,1000		; Otherwise, get 1000s part
		div	bp
		test	ax,ax		; Above 1000?
		jz	.hundreds_dx	; If not, just find hundreds
		push	dx		; Otherwise, save <1000s part,
		call	.hundreds	; get string for how many thousands,
		mov	si,thousand	; Then add ' thousand',
		call	stradd
		pop	dx		; Restore <1000 part,
		test	dx,dx		; Even thousands?
		jnz	.hundreds_spc	; Then add hundreds
		ret			; Otherwise we're done
.hundreds_spc:	mov	al,' '		; Add space betweeen thousand and rest
		stosb
.hundreds_dx:	mov	ax,dx		
.hundreds:	mov	bp,100		; Get hundreds part
		xor	dx,dx
		div	bp		; AX=100s
		test	ax,ax		; If zero, no hundreds
		jz	.tens_dx
		dec	ax		; Otherwise, look up in singles
		shl	ax,1		; table,
		mov	bx,ax
		mov	si,[single+bx]
		call	stradd		; Add to the output string,
		mov	si,hundred	; Add ' hundred',
		call	stradd
		test	dx,dx		; Is there any more?
		jne	.tens_spc	; If so, add tens
		ret			; Otherwise we're done
.tens_spc:	mov	al,' '		; Add space between 'hundred' and tens
		stosb
.tens_dx:	mov	ax,dx		; Tens in AX (from hundreds)
.tens:		aam			; AH=10s digit, AL=1s digit
		test	ah,ah		; If 10s digit is 0, single digit
		jz	.ones
		cmp	ah,1		; If 10s digit is 1, teens
		jz	.teens
		mov	bl,ah		; Look up tens digit in tens table
		sub	bl,2
		shl	bl,1
		xor	bh,bh
		mov	si,[tens+bx]	; Add to the output string
		call	stradd
		test	al,al		; Ones digit left?
		jne	.ones_dash	; If so, add dash and ones digit
		ret			; Otherwise we're done
.ones_dash:	mov	[di],byte '-'
		inc	di
.ones:		mov	bl,al		; Look up ones digit in ones table
		dec	bl
		shl	bl,1
		xor	bh,bh
		mov	si,[single+bx]
		jmp	stradd
.teens:		mov	bl,al		; Look up ones digit in teens table
		shl	bl,1
		xor	bh,bh
		mov	si,[teens+bx]
		jmp	stradd 
.zero:		mov	si,zero
		;;;	Copy $-terminated string at DS:SI to ES:DI, except
		;;;	the terminator.
stradd:		push	ax		; Keep AX register
.loop:		lodsb			; Get byte from DS:SI
		cmp 	al,'$'		; Are we there yet?
		je	.out		; If so, stop
		stosb			; Otherwise, store at ES:DI
		jmp	.loop
.out:		pop	ax
		ret
section		.data
single:		dw	one,two,three,four
		dw	five,six,seven,eight,nine
teens:		dw	ten,eleven,twelve,thirteen,fourteen
		dw	fifteen,sixteen,seventeen,eighteen,nineteen 
tens:		dw	twenty,thirty,forty,fifty
		dw	sixty,seventy,eighty,ninety
zero:		db	'zero$'
one:		db	'one$'
two:		db	'two$'
three:		db	'three$'
four:		db	'four$'
five:		db	'five$'
six:		db	'six$'
seven:		db	'seven$'
eight:		db	'eight$'
nine:		db	'nine$'
ten:		db	'ten$'
eleven:		db	'eleven$'
twelve:		db	'twelve$'
thirteen:	db	'thirteen$'
fourteen:	db	'fourteen$'
fifteen:	db	'fifteen$'
sixteen:	db	'sixteen$'
seventeen:	db	'seventeen$'
eighteen:	db	'eighteen$'
nineteen:	db	'nineteen$'
twenty:		db	'twenty$'
thirty:		db	'thirty$'
forty:		db	'forty$'
fifty:		db	'fifty$'
sixty:		db	'sixty$'
seventy:	db	'seventy$'
eighty:		db	'eighty$'
ninety:		db	'ninety$'
hundred:	db	' hundred$'
thousand:	db	' thousand$'
is:		db	' is $'
magic:		db	'magic.$'
commaspace:	db	', $'
errinput:	db	'No input$'
errhigh:	db	'Max input 999999$'
section		.bss
numstring:	resb	1024