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


MODULE FOUR_IS_MAGIC
    IMPLICIT NONE
    CHARACTER(8), DIMENSION(20) :: SMALL_NUMS
    CHARACTER(7), DIMENSION(8) :: TENS
    CHARACTER(7) :: HUNDRED
    CHARACTER(8) :: THOUSAND
    CHARACTER(8) :: MILLION
    CHARACTER(8) :: BILLION
    CHARACTER(9) :: TRILLION
    CHARACTER(11) :: QUADRILLION
    CHARACTER(11) :: QUINTILLION
    CHARACTER(1) :: SEPARATOR
    CHARACTER(1) :: SPACE
 
 
    CONTAINS
 
    SUBROUTINE INIT_ARRAYS
 
        SMALL_NUMS(1) = "zero"
        SMALL_NUMS(2) = "one"
        SMALL_NUMS(3) = "two"
        SMALL_NUMS(4) = "three"
        SMALL_NUMS(5) = "four"
        SMALL_NUMS(6) = "five"
        SMALL_NUMS(7) = "six"
        SMALL_NUMS(8) = "seven"
        SMALL_NUMS(9) = "eight"
        SMALL_NUMS(10) = "nine"
        SMALL_NUMS(11) = "ten"
        SMALL_NUMS(12) = "eleven"
        SMALL_NUMS(13) = "twelve"
        SMALL_NUMS(14) = "thirteen"
        SMALL_NUMS(15) = "fourteen"
        SMALL_NUMS(16) = "fifteen"
        SMALL_NUMS(17) = "sixteen"
        SMALL_NUMS(18) = "seventeen"
        SMALL_NUMS(19) = "eighteen"
        SMALL_NUMS(20) = "nineteen"
 
        TENS(1) = "twenty"
        TENS(2) = "thirty"
        TENS(3) = "forty"
        TENS(4) = "fifty"
        TENS(5) = "sixty"
        TENS(6) = "seventy"
        TENS(7) = "eight"
        TENS(8) = "ninety"
 
        HUNDRED = "hundred"
        THOUSAND = "thousand"
        MILLION = "million"
        BILLION = "billion"
        TRILLION = "trillion"
        QUADRILLION = "quadrillion"
        QUINTILLION = "quintillion"
        SEPARATOR = "-"
        SPACE = " "
 
    END SUBROUTINE INIT_ARRAYS
 
    RECURSIVE FUNCTION STRING_REPRESENTATION(NUM) RESULT(NUM_AS_STR)
        INTEGER(16), INTENT(IN) :: NUM
        CHARACTER(1000) :: NUM_AS_STR
        INTEGER(16), DIMENSION(9) :: COMPONENTS
 
        CALL INIT_ARRAYS()
 
        COMPONENTS = GET_COMPONENTS(NUM)
 
        NUM_AS_STR = TRIM(ADJUSTL(GET_SUBSET(COMPONENTS(9), QUINTILLION)))
        NUM_AS_STR = TRIM(NUM_AS_STR) // SPACE // TRIM(ADJUSTL(GET_SUBSET(COMPONENTS(8), QUADRILLION)))
        NUM_AS_STR = TRIM(NUM_AS_STR) // SPACE // TRIM(ADJUSTL(GET_SUBSET(COMPONENTS(7), TRILLION)))
        NUM_AS_STR = TRIM(NUM_AS_STR) // SPACE // TRIM(ADJUSTL(GET_SUBSET(COMPONENTS(6), BILLION)))
        NUM_AS_STR = TRIM(NUM_AS_STR) // SPACE // TRIM(ADJUSTL(GET_SUBSET(COMPONENTS(5), MILLION)))
        NUM_AS_STR = TRIM(NUM_AS_STR) // SPACE // TRIM(ADJUSTL(GET_SUBSET(COMPONENTS(4), THOUSAND)))
        NUM_AS_STR = TRIM(NUM_AS_STR) // SPACE // TRIM(ADJUSTL(GET_SUBSET(COMPONENTS(3), HUNDRED)))
 
 
        IF (COMPONENTS(2) .EQ. 1) THEN
            NUM_AS_STR = TRIM(ADJUSTL(NUM_AS_STR)) // SPACE // TRIM(ADJUSTL(SMALL_NUMS(10 + COMPONENTS(1) + 1)))
        ELSE
 
            IF (COMPONENTS(1) .GT. 0) THEN
                IF (COMPONENTS(2) .GT. 0) THEN
                    NUM_AS_STR = TRIM(ADJUSTL(NUM_AS_STR)) // SPACE // TRIM(ADJUSTL(TENS(COMPONENTS(2) - 1))) // SEPARATOR
                    NUM_AS_STR = TRIM(ADJUSTL(NUM_AS_STR)) // TRIM(ADJUSTL(SMALL_NUMS(COMPONENTS(1) + 1)))
                ELSE
                    NUM_AS_STR = TRIM(ADJUSTL(NUM_AS_STR)) // SPACE // TRIM(ADJUSTL(SMALL_NUMS(COMPONENTS(1) + 1)))
                ENDIF
            ELSE IF (COMPONENTS(2) .GT. 0) THEN
                NUM_AS_STR = TRIM(ADJUSTL(NUM_AS_STR)) // SPACE // TRIM(ADJUSTL(TENS(COMPONENTS(2) - 1)))
            ENDIF
        ENDIF
 
    END FUNCTION STRING_REPRESENTATION
 
    FUNCTION GET_COMPONENTS(NUM)
        INTEGER(16), INTENT(IN) :: NUM
        INTEGER(16), DIMENSION(9) :: GET_COMPONENTS
        INTEGER(16) :: I_UNITS
        INTEGER(16) :: I_TENS
        INTEGER(16) :: I_HUNDREDS
        INTEGER(16) :: I_THOUSANDS
        INTEGER(16) :: I_MILLIONS
        INTEGER(16) :: I_BILLIONS
        INTEGER(16) :: I_TRILLIONS
        INTEGER(16) :: I_QUADRILLIONS
        INTEGER(16) :: I_QUINTILLIONS
        REAL(16) DIVIDE_TEMP
 
        I_UNITS = NUM
 
        DIVIDE_TEMP = (I_UNITS - MOD(I_UNITS, 1000000000000000000))/1000000000000000000
        I_QUINTILLIONS = FLOOR(DIVIDE_TEMP)
 
        IF (I_QUINTILLIONS .NE. 0) THEN
           I_UNITS = I_UNITS - I_QUINTILLIONS*1000000000000000000
        ENDIF
 
 
        DIVIDE_TEMP = (I_UNITS - MOD(I_UNITS, 1000000000000000))/1000000000000000
        I_QUADRILLIONS = FLOOR(DIVIDE_TEMP)
 
        IF (I_QUADRILLIONS .NE. 0) THEN
           I_UNITS = I_UNITS - I_QUADRILLIONS*1000000000000000
        ENDIF
 
        DIVIDE_TEMP = (I_UNITS - MOD(I_UNITS, 1000000000000))/1000000000000
        I_TRILLIONS = FLOOR(DIVIDE_TEMP)
 
        IF (I_TRILLIONS .NE. 0) THEN
           I_UNITS = I_UNITS - I_TRILLIONS*1000000000000
        ENDIF
 
        DIVIDE_TEMP = (I_UNITS - MOD(I_UNITS, 1000000000))/1000000000
        I_BILLIONS = FLOOR(DIVIDE_TEMP)
 
        IF (I_BILLIONS .NE. 0) THEN
           I_UNITS = I_UNITS - I_BILLIONS*1000000000
        ENDIF
 
        DIVIDE_TEMP = (I_UNITS - MOD(I_UNITS, 1000000))/1000000
 
        I_MILLIONS = FLOOR(DIVIDE_TEMP)
 
        IF (I_MILLIONS .NE. 0) THEN
           I_UNITS = I_UNITS - I_MILLIONS*1000000
        ENDIF
 
        DIVIDE_TEMP = (I_UNITS - MOD(I_UNITS, 1000))/1000
 
        I_THOUSANDS = FLOOR(DIVIDE_TEMP)
 
        IF (I_THOUSANDS .NE. 0) THEN
           I_UNITS = I_UNITS - I_THOUSANDS*1000
        ENDIF
 
        DIVIDE_TEMP = I_UNITS/1E2
        I_HUNDREDS = FLOOR(DIVIDE_TEMP)
 
        IF (I_HUNDREDS .NE. 0) THEN
           I_UNITS = I_UNITS - I_HUNDREDS*1E2
        ENDIF
 
        DIVIDE_TEMP = I_UNITS/10.
        I_TENS =  FLOOR(DIVIDE_TEMP)
 
        IF (I_TENS .NE. 0) THEN
           I_UNITS = I_UNITS - I_TENS*10
        ENDIF
 
        GET_COMPONENTS(1) = I_UNITS
        GET_COMPONENTS(2) = I_TENS
        GET_COMPONENTS(3) = I_HUNDREDS
        GET_COMPONENTS(4) = I_THOUSANDS
        GET_COMPONENTS(5) = I_MILLIONS
        GET_COMPONENTS(6) = I_BILLIONS
        GET_COMPONENTS(7) = I_TRILLIONS
        GET_COMPONENTS(8) = I_QUADRILLIONS
        GET_COMPONENTS(9) = I_QUINTILLIONS
 
    END FUNCTION GET_COMPONENTS
 
    FUNCTION GET_SUBSET(COUNTER, LABEL) RESULT(OUT_STR)
        CHARACTER(*), INTENT(IN) :: LABEL
        INTEGER(16), INTENT(IN) :: COUNTER
        CHARACTER(100) :: OUT_STR
 
        OUT_STR = ""
 
        IF (COUNTER .GT. 0) THEN
            IF (COUNTER .LT. 20) THEN
                OUT_STR = SPACE // TRIM(ADJUSTL(SMALL_NUMS(COUNTER + 1)))
            ELSE
                OUT_STR = SPACE // TRIM(ADJUSTL(STRING_REPRESENTATION(COUNTER)))
            ENDIF
            OUT_STR = TRIM(ADJUSTL(OUT_STR)) // SPACE // TRIM(LABEL)
        ENDIF
 
    END FUNCTION GET_SUBSET
 
 
    SUBROUTINE FIND_MAGIC(NUM)
        INTEGER(16), INTENT(IN) :: NUM
        INTEGER(16) :: CURRENT, LEN_SIZE
        CHARACTER(1000) :: CURRENT_STR, CURRENT_STR_LEN
        CHARACTER(1000) :: OUT_STR
 
        CURRENT = NUM
        OUT_STR = ""
 
        DO WHILE (CURRENT .NE. 4)
            CURRENT_STR = STRING_REPRESENTATION(CURRENT)
            LEN_SIZE = LEN_TRIM(ADJUSTL(CURRENT_STR))
            CURRENT_STR_LEN = STRING_REPRESENTATION(LEN_SIZE)
            OUT_STR = TRIM(ADJUSTL(OUT_STR)) // SPACE // TRIM(ADJUSTL(CURRENT_STR))
            OUT_STR = TRIM(ADJUSTL(OUT_STR)) // " is " // TRIM(ADJUSTL(CURRENT_STR_LEN)) // ","
            CURRENT = LEN_SIZE
        ENDDO
 
        WRITE(*,*) TRIM(ADJUSTL(OUT_STR)) // SPACE // "four is magic."
    END SUBROUTINE FIND_MAGIC
 
 
END MODULE FOUR_IS_MAGIC
 
PROGRAM TEST_NUM_NAME
        USE FOUR_IS_MAGIC
        IMPLICIT NONE
 
        INTEGER(2) I
        INTEGER(16), DIMENSION(10) :: TEST_NUMS = (/5, 13, 78, 797, 2739, 4000, 7893, 93497412, 2673497412, 10344658531277200972/)
        CHARACTER(1000) :: NUM_NAME
 
        DO I=1, SIZE(TEST_NUMS)
            CALL FIND_MAGIC(TEST_NUMS(I))
        ENDDO
END PROGRAM
 