#lang racket/base
(require racket/contract)
(provide
(contract-out
[number->cardinal [countable-number? . -> . string?]])
(rename-out [number->cardinal number->cardinal/uncontracted])
maximum-countable-cardinal-number)
(define (countable-number? n)
(and (exact-nonnegative-integer? n)
(<= n maximum-countable-cardinal-number)))
(define (number->cardinal n)
(cond
[(< n 1000)
(small-number->cardinal n)]
[else
(define this-mag (mag10 n))
(cond
[(>= this-mag max-mag)
(error 'number->cardinal "~s too large to count" n)]
[else
(define s (quotient this-mag 3))
(define t (expt 10 (* 3 s)))
(if (= 0 (remainder n t))
(string-append (small-number->cardinal (quotient n t))
" "
(vector-ref scales s))
(string-append (small-number->cardinal (quotient n t))
" " (vector-ref scales s)
" " (number->cardinal (remainder n t))))])]))
(define (small-number->cardinal n)
(cond
[(< n 21)
(vector-ref small-cardinals n)]
[(< n 100)
(if (= 0 (remainder n 10))
(vector-ref tens (quotient n 10))
(string-append (vector-ref tens (quotient n 10))
"-"
(vector-ref small-cardinals (remainder n 10))))]
[(< n 1000)
(if (= 0 (remainder n 100))
(string-append (small-number->cardinal (quotient n 100))
" hundred")
(string-append (small-number->cardinal (quotient n 100))
" hundred "
(small-number->cardinal (remainder n 100))))]))
(define small-cardinals #("zero" "one" "two" "three" "four"
"five" "six" "seven" "eight" "nine" "ten"
"eleven" "twelve" "thirteen" "fourteen"
"fifteen" "sixteen" "seventeen" "eighteen"
"nineteen" "twenty"))
(define tens #(""
"ten" "twenty" "thirty" "forty"
"fifty" "sixty" "seventy" "eighty"
"ninety"))
(define scales
#(""
"thousand" "million" "billion" "trillion" "quadrillion" "quintillion" "sextillion"
"septillion"
"octillion"
"nonillion"
"decillion"
"undecillion"
"duodecillion"
"tredecillion"
"quattuordecillion"
"quindecillion"
"sexdecillion"
"septendecillion"
"octodecillion"
"novemdecillion"
"vigintillion"
))
(define (mag10 n)
(let loop ([n n]
[x 0])
(cond [(= 0 (quotient n 10))
x]
[else
(loop (quotient n 10)
(add1 x))])))
(define max-mag (* 3 (vector-length scales)))
(define maximum-countable-cardinal-number
(sub1 (expt 10 max-mag)))