I wrote a numbers-to-words converter for English, then tried to adapt the code to work for French.
I'm wondering mainly whether any simplifications are possible.
In English
English is relatively straightforward:
english :: Int -> String
english n
| n == 0 = "zero"
| n < 0 = "negative " ++ (english (-n))
| otherwise = kScale 0 $ map wordsTo1k $ splitk $ littleEndianDigits n
where
littleEndianDigits n
| n < 0 = error "Negative"
| n < 10 = [n]
| otherwise = (n `mod` 10) : (littleEndianDigits (n `div` 10))
splitk [] = []
splitk (i:[]) = [(0, 0, i)]
splitk (i:x:[]) = [(0, x, i)]
splitk (i:x:c:n) = (c, x, i) : (splitk n)
wordsTo1k (0, 0, i) = digits !! i
wordsTo1k (0, x, 0) = tens !! x
wordsTo1k (0, 1, i) = teens !! i
wordsTo1k (0, x, i) = (wordsTo1k (0, x, 0)) ++ "-" ++ (wordsTo1k (0, 0, i))
wordsTo1k (c, 0, 0) = (digits !! c) ++ " hundred"
wordsTo1k (c, x, i) = (wordsTo1k (c, 0, 0)) ++ " " ++ (wordsTo1k (0, x, i))
kScale s [] = []
kScale s ("zero":gs) = kScale (s + 1) gs
kScale s (g:gs) = unwords' [ kScale (s + 1) gs, g, kScales !! s ]
unwords' (a:[]) = a
unwords' (a:[""]) = a
unwords' ([]:b) = unwords' b
unwords' (a:b) = a ++ " " ++ unwords' b
digits = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
tens = [error "zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
teens = [error "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"]
kScales = [[], "thousand", "million", "billion", "trillion", "quadrillion", "quintillion"]
En français
I've tried to stick to "standard" French, using the style guide from Le Figaro and these rules for large numbers.
Relative to English, complications include:
- Numbers 61 to 99
- Pluralization inflections: 100 is cent, 200 is deux cents, but 201 loses the "s" (deux cent un). "Thousand" is mille, and it is invariable — 2000 is deux mille.
- "One hundred" is just cent, "one thousand" is mille, but "one million" is un million.
- Large numbers use the long scale.
I hope I have interpreted the rules correctly!
french :: Int -> String
french n
| n == 0 = "zéro"
| n < 0 = "moins " ++ (french (-n))
| otherwise = mScale 0 $ map wordsTo1M $ group2 $ splitk $ littleEndianDigits n
where
littleEndianDigits n
| n < 0 = error "Negative"
| n < 10 = [n]
| otherwise = (n `mod` 10) : (littleEndianDigits (n `div` 10))
splitk [] = []
splitk (i:[]) = [(0, 0, i)]
splitk (i:x:[]) = [(0, x, i)]
splitk (i:x:c:n) = (c, x, i) : (splitk n)
wordsTo1k (0, 0, i) = digits !! i
wordsTo1k (0, 8, 0) = (tens !! 8) ++ "s"
wordsTo1k (0, x, 0) = tens !! x
wordsTo1k (0, 1, i) = teens !! i
wordsTo1k (0, 7, 1) = (tens !! 6) ++ "-et-" ++ (wordsTo1k (0, 1, 1))
wordsTo1k (0, 7, i) = (tens !! 6) ++ "-" ++ (wordsTo1k (0, 1, i))
wordsTo1k (0, 8, i) = (tens !! 8) ++ "-" ++ (wordsTo1k (0, 0, i))
wordsTo1k (0, 9, i) = (tens !! 8) ++ "-" ++ (wordsTo1k (0, 1, i))
wordsTo1k (0, x, 1) = (wordsTo1k (0, x, 0)) ++ "-et-" ++ (wordsTo1k (0, 0, 1))
wordsTo1k (0, x, i) = (wordsTo1k (0, x, 0)) ++ "-" ++ (wordsTo1k (0, 0, i))
wordsTo1k (1, 0, 0) = "cent"
wordsTo1k (1, x, i) = "cent " ++ (wordsTo1k (0, x, i))
wordsTo1k (c, 0, 0) = (digits !! c) ++ " cents"
wordsTo1k (c, x, i) = (digits !! c) ++ " cent " ++ (wordsTo1k (0, x, i))
group2 [] = []
group2 (k0:[]) = ((0, 0, 0), k0) : []
group2 (k0:k1:ks) = (k1, k0) : group2 ks
wordsTo1M ((0, 0, 0), ones) = wordsTo1k ones
wordsTo1M ((0, 0, 1), (0, 0, 0)) = "mille"
wordsTo1M ((0, 0, 1), ones) = "mille " ++ (wordsTo1k ones)
wordsTo1M (thousands, (0, 0, 0)) = (wordsTo1k thousands) ++ " mille"
wordsTo1M (thousands, ones) = (wordsTo1k thousands) ++ " mille " ++ (wordsTo1k ones)
mScale s [] = []
mScale s ("zéro":gs) = mScale (s + 1) gs
mScale s (g:gs)
| s == 0 || g == "un" = unwords' [ mScale (s + 1) gs, g, mScales !! s ]
| otherwise = unwords' [ mScale (s + 1) gs, g, mScales !! s ++ "s" ]
unwords' (a:[]) = a
unwords' (a:[""]) = a
unwords' ([]:b) = unwords' b
unwords' (a:b) = a ++ " " ++ unwords' b
digits = ["zéro", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf"]
tens = [error "zero", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "soixante-dix", "quatre-vingt", "quatre-vingt-dix"]
teens = [error "ten", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf"]
mScales = [[], "million", "billion", "trillion"]
Sample output
*Main> map (\n -> (n, english n)) [1,2,10,11,100,101,1000,1001,2000,100000,1000000,2000000,2000001,1000000000,1000600003,2001000003000,-123456789,maxBound::Int]
[(1,"one"),
(2,"two"),
(10,"ten"),
(11,"eleven"),
(100,"one hundred"),
(101,"one hundred one"),
(1000,"one thousand"),
(1001,"one thousand one"),
(2000,"two thousand"),
(100000,"one hundred thousand"),
(1000000,"one million"),
(2000000,"two million"),
(2000001,"two million one"),
(1000000000,"one billion"),
(1000600003,"one billion six hundred thousand three"),
(2001000003000,"two trillion one billion three thousand"),
(-123456789,"negative one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine"),
(9223372036854775807,"nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven")]
*Main> map (\n -> (n, french n)) [1,2,10,11,100,101,1000,1001,2000,100000,1000000,2000000,2000001,1000000000,1000600003,2001000003000,-123456789,maxBound::Int]
[(1,"un"),
(2,"deux"),
(10,"dix"),
(11,"onze"),
(100,"cent"),
(101,"cent un"),
(1000,"mille"),
(1001,"mille un"),
(2000,"deux mille"),
(100000,"cent mille"),
(1000000,"un million"),
(2000000,"deux millions"),
(2000001,"deux millions un"),
(1000000000,"mille millions"),
(1000600003,"mille millions six cents mille trois"),
(2001000003000,"deux billions mille millions trois mille"),
(-123456789,"moins cent vingt-trois millions quatre cent cinquante-six mille sept cent quatre-vingt-neuf"),
(9223372036854775807,"neuf trillions deux cent vingt-trois mille trois cent soixante-douze billions trente-six mille huit cent cinquante-quatre millions sept cent soixante-quinze mille huit cent sept")]
4 * 20 + 7
and for 97 it should be4 * 20 + 17
. According to my memory and Google, 87 isquatre vingt sept
andquatre vingt dix-sept
for 97. \$\endgroup\$vingt et un
but 22 isvingt-deux
. \$\endgroup\$Int
s fromminBound
tomaxBound
. If you find a counterexample, please write an answer. \$\endgroup\$