(define-syntax %numspell:testeez
(syntax-rules () ((_ x ...)
(error "Tests disabled.")
)))
(define-syntax %numspell:call-with-output-string
(syntax-rules ()
((_ PROC)
(let ((port (open-output-string)))
(PROC port)
(let ((str (get-output-string port)))
(close-output-port port)
str)))))
(define %numspell:short-scale-english
'(#f
"thousand"
"million"
"billion"
"trillion"
"quadrillion"
"quintillion"
"sextillion"
"septillion"
"octillion"
"nonillion"
"decillion"
"undecillion"
"deuodecillion"
"tredecillion"
"quattuordecillion"
"quindecillion"
"sexdecillion"
"septendecillion"
"octodecillion"
"novemdecillion"
"vigintillion"
))
(define %numspell:long-scale-english
'(#f
"thousand"
"million" "thousand million"
"billion" "thousand billion"
"trillion" "thousand trillion"
"quadrillion" "thousand quadrillion"
"quintillion" "thousand quintillion"
"sextillion" "thousand sextillion"
"septillion" "thousand septillion"
"octillion" "thousand octillion"
"nonillion" "thousand nonillion"
"decillion" "thousand decillion"
"undecillion" "thousand undecillion"
"deuodecillion" "thousand deuodecillion"
"tredecillion" "thousand tredecillion"
"quattuordecillion" "thousand quattuordecillion"
"quindecillion" "thousand quindecillion"
"sexdecillion" "thousand sexdecillion"
"septendecillion" "thousand septendecillion"
"octodecillion" "thousand octodecillion"
"novemdecillion" "thousand novemdecillion"
"vigintillion" "thousand vigintillion"
))
(define (write-number-as-english num port)
(write-number-as-short-scale-english num port))
(define (write-number-as-short-scale-english num port)
(%numspell:spell-number num port %numspell:short-scale-english))
(define (write-number-as-long-scale-english num port)
(%numspell:spell-number num port %numspell:long-scale-english))
(define (%numspell:spell-number num port scale)
(cond
((not (number? num)) (error "not a number:" num))
((integer? num ) (%numspell:spell-integer num port scale))
((rational? num ) (%numspell:spell-noninteger num port scale))
(else (error "cannot spell number:" num))))
(define (%numspell:spell-integer num port scale)
(or (integer? num) (error "not an integer:" num))
(let spell ((num num))
(if (< num 0)
(begin (display "negative " port)
(spell (- num)))
(%numspell:spell-nonnegative-integer num port scale))))
(define (%numspell:spell-integer-substring str start end port scale)
(%numspell:spell-integer (string->number (substring str start end))
port
scale))
(define (%numspell:spell-noninteger num port scale)
(or (and (number? num) (rational? num)) (error "not a rational number:" num))
(let spell ((num num))
(if (< num 0)
(begin (display "negative " port)
(spell (- num)))
(%numspell:spell-nonnegative-noninteger num port scale))))
(define %numspell:spell-nonnegative-integer
(letrec ((split-integer
(lambda (num divisor)
(let ((first (truncate (/ num divisor))))
(values first (- num (* first divisor))))))
(zero-through-nineteen
'#("zero" "one" "two" "three" "four" "five" "six" "seven" "eight"
"nine" "ten" "eleven" "twelve" "thirteen" "fourteen" "fifteen"
"sixteen" "seventeen" "eighteen" "nineteen"))
(twenty-through-ninety
'#("twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty"
"ninety")))
(lambda (num port scale)
(let loop ((num num)
(names scale))
(let-values (((thousands nonthousands) (split-integer num 1000)))
(or (zero? thousands) (loop thousands (cdr names)))
(if (zero? nonthousands)
(if (zero? thousands)
(display "zero" port))
(let-values (((hundreds nonhundreds)
(split-integer nonthousands 100)))
(or (zero? hundreds)
(begin
(or (zero? thousands)
(write-char #\space port))
(display (vector-ref zero-through-nineteen hundreds)
port)
(display " hundred" port)))
(or (zero? nonhundreds)
(begin
(or (and (zero? thousands) (zero? hundreds))
(write-char #\space port))
(if (< nonhundreds 20)
(display (vector-ref zero-through-nineteen
nonhundreds)
port)
(let-values (((tens ones)
(split-integer nonhundreds 10)))
(display (vector-ref twenty-through-ninety
(- tens 2))
port)
(or (zero? ones)
(begin
(write-char #\- port)
(display (vector-ref zero-through-nineteen
ones)
port)))))))
(cond ((car names) => (lambda (scale)
(write-char #\space port)
(display scale port)))))))))))
(define (%numspell:spell-nonnegative-noninteger num port scale)
(or (and (number? num) (rational? num))
(error "wrong kind of number:" num))
(let* ((str (number->string num))
(len (string-length str)))
(let loop-for-point ((i 0))
(if (= i len)
(error "number string empty:" num str)
(case (string-ref str i)
((#\/)
(if (zero? i)
(display "zero" port)
(%numspell:spell-integer-substring str 0 i port scale))
(let ((start (+ 1 i)))
(let loop-for-decimal-digits ((i start))
(if (= i len)
(if (= start i)
(error "number string empty after slash:" num str)
(begin
(display " over " port)
(%numspell:spell-integer-substring
str start i port scale)))
(case (string-ref str i)
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
(loop-for-decimal-digits (+ 1 i)))
(else
(error
"number string has unknown character after slash:"
num str i)))))))
((#\. #\,)
(if (zero? i)
(display "zero" port)
(%numspell:spell-integer-substring str 0 i port scale))
(display " point" port)
(if (= (+ 1 i) len)
(display " zero" port)
(let loop-for-decimal-digits ((i (+ 1 i)))
(if (< i len)
(begin
(display
(case (string-ref str i)
((#\0) " zero")
((#\1) " one")
((#\2) " two")
((#\3) " three")
((#\4) " four")
((#\5) " five")
((#\6) " six")
((#\7) " seven")
((#\8) " eight")
((#\9) " nine")
(else
(error "cannot spell number with string:"
num str)))
port)
(loop-for-decimal-digits (+ 1 i)))))))
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
(loop-for-point (+ 1 i)))
(else (error "cannot spell number with string:" num str)))))))
(define (number->english num)
(number->short-scale-english num))
(define (number->short-scale-english num)
(%numspell:call-with-output-string
(lambda (port)
(write-number-as-short-scale-english num port))))
(define (number->long-scale-english num)
(%numspell:call-with-output-string
(lambda (port)
(write-number-as-long-scale-english num port))))
(define (%numspell:test)
(%numspell:testeez
"numspell.scm"
(test/equal "" (number->english 0) "zero")
(test/equal "" (number->english 1) "one")
(test/equal "" (number->english 2) "two")
(test/equal "" (number->english 3) "three")
(test/equal "" (number->english 4) "four")
(test/equal "" (number->english 5) "five")
(test/equal "" (number->english 6) "six")
(test/equal "" (number->english 7) "seven")
(test/equal "" (number->english 8) "eight")
(test/equal "" (number->english 9) "nine")
(test/equal "" (number->english 10) "ten")
(test/equal "" (number->english 11) "eleven")
(test/equal "" (number->english 12) "twelve")
(test/equal "" (number->english 13) "thirteen")
(test/equal "" (number->english 14) "fourteen")
(test/equal "" (number->english 15) "fifteen")
(test/equal "" (number->english 16) "sixteen")
(test/equal "" (number->english 17) "seventeen")
(test/equal "" (number->english 18) "eighteen")
(test/equal "" (number->english 19) "nineteen")
(test/equal "" (number->english 20) "twenty")
(test/equal "" (number->english 21) "twenty-one")
(test/equal "" (number->english 30) "thirty")
(test/equal "" (number->english 40) "forty")
(test/equal "" (number->english 50) "fifty")
(test/equal "" (number->english 60) "sixty")
(test/equal "" (number->english 70) "seventy")
(test/equal "" (number->english 80) "eighty")
(test/equal "" (number->english 90) "ninety")
(test/equal "" (number->english 100) "one hundred")
(test/equal "" (number->english 102) "one hundred two")
(test/equal "" (number->english 1002) "one thousand two")
(test/equal "" (number->english 10002) "ten thousand two")
(test/equal "" (number->english 100002) "one hundred thousand two")
(test/equal "" (number->english 1000002) "one million two")
(test/equal "" (number->english 10000002) "ten million two")
(test/equal "" (number->english 100000002) "one hundred million two")
(test/equal "" (number->english 1000000002) "one billion two")
(test/equal "" (number->english 1000000000002) "one trillion two")
(test/equal "" (number->english 100020003000) "one hundred billion twenty million three thousand")
(test/equal ""
(number->english 123)
"one hundred twenty-three")
(test/equal ""
(number->english 1234)
"one thousand two hundred thirty-four")
(test/equal ""
(number->english 12345)
"twelve thousand three hundred forty-five")
(test/equal ""
(number->english 123456)
"one hundred twenty-three thousand four hundred fifty-six")
(test/equal
""
(number->english 1234567)
"one million two hundred thirty-four thousand five hundred sixty-seven")
(test/equal
""
(number->english 123456789012345678901234567890)
"one hundred twenty-three octillion four hundred fifty-six septillion seven hundred eighty-nine sextillion twelve quintillion three hundred forty-five quadrillion six hundred seventy-eight trillion nine hundred one billion two hundred thirty-four million five hundred sixty-seven thousand eight hundred ninety")
(test/equal ""
(number->english 1/3)
"one over three")
(test/equal ""
(number->english 123.0123)
"one hundred twenty-three point zero one two three")
(test-define ""
f
(lambda (n)
(let ((x (expt 10 n)))
(list (number->short-scale-english x)
(number->long-scale-english x)))))
(test/equal "" (f 3) '("one thousand" "one thousand"))
(test/equal "" (f 6) '("one million" "one million"))
(test/equal "" (f 9) '("one billion" "one thousand million"))
(test/equal "" (f 12) '("one trillion" "one billion"))
(test/equal "" (f 15) '("one quadrillion" "one thousand billion"))
(test/equal "" (f 18) '("one quintillion" "one trillion"))
(test/equal "" (number->english -1) "negative one")
))