1static ONES: [&str; 10] = [
49 "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
50];
51
52static TEENS: [&str; 10] = [
53 "ten",
54 "eleven",
55 "twelve",
56 "thirteen",
57 "fourteen",
58 "fifteen",
59 "sixteen",
60 "seventeen",
61 "eighteen",
62 "nineteen",
63];
64
65static TENS: [&str; 10] = [
66 "", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety",
67];
68static THOUSANDS: [&str; 5] = ["", "thousand", "million", "billion", "trillion"];
70
71const ASCII_ZERO_OFFSET: u8 = 48;
72const LARGEST_ALLOWABLE_INPUT_VALUE: f64 = 9_999_999_999_999.99;
73
74pub fn number_to_words<T: std::convert::Into<f64>>(
75 number: T,
76 should_capitalise_first_word: bool,
77) -> String {
78 let number = num::abs(number.into());
80 if number > LARGEST_ALLOWABLE_INPUT_VALUE {
81 return "number too large".to_owned();
82 }
83 let formatted_num: String = round_and_format_number(number);
84 let split_number = split_on_decimal_point(formatted_num);
85 let mantissa = split_number[0].clone();
86 let mut cents = split_number[1].clone();
87 let mut all_zeros = true;
88 let mut should_skip_next_iteration = false;
89 let mut result: String = String::new();
90 let mut temp: String;
91
92 let mut mantissa = mantissa.into_bytes();
94
95 for _digit in mantissa.iter_mut() {
97 *_digit -= ASCII_ZERO_OFFSET;
98 }
99 for i in (0..mantissa.len()).rev() {
101 if should_skip_next_iteration {
102 should_skip_next_iteration = false;
103 continue;
104 }
105 let next_digit = mantissa[i];
106 let column = mantissa.len() - (i + 1);
107
108 match column % 3 {
110 0 => {
111 let mut show_thousands = true;
113 if i == 0 {
114 temp = ONES[next_digit as usize].to_string() + " ";
115 } else if mantissa[i - 1] == 1 {
116 temp = TEENS[next_digit as usize].to_owned() + " ";
118 should_skip_next_iteration = true;
120 } else if next_digit != 0 {
121 temp = ONES[next_digit as usize].to_owned() + " ";
123 } else {
124 temp = String::new();
127 show_thousands = mantissa[i - 1] != 0 || (i > 1 && mantissa[i - 2] != 0);
128 }
129 if show_thousands {
131 if column > 0 {
132 temp = temp
133 + &(THOUSANDS[column / 3].to_owned()
134 + if all_zeros { " " } else { ", " });
135 }
136 all_zeros = false;
138 }
139 result = (temp.clone() + &result).to_owned();
140 }
141 1 => {
142 result = handle_tens(next_digit.into(), i, mantissa.clone()) + &result;
144 }
145 2 => {
146 if next_digit > 0 {
148 temp = ONES[next_digit as usize].to_owned() + " hundred ";
149 result = temp + &result;
150 }
151 }
152 _ => {
153 }
155 }
156 }
157
158 if should_capitalise_first_word {
159 result = capitalise_first_letter(result);
160 }
161 if cents.starts_with('0') {
163 cents.remove(0);
164 }
165 if cents == "0" {
167 result.pop();
169 result
170 } else {
171 result + "and " + ¢s + "/100"
172 }
173}
174
175fn round_and_format_number(num: f64) -> String {
176 format!("{:.2}", f64::round(num * 100.0) / 100.0)
177}
178
179fn handle_tens(next: usize, idx: usize, mantissa: Vec<u8>) -> String {
180 if next > 0 {
181 return TENS[next].to_owned() + (if mantissa[idx + 1] != 0 { "-" } else { " " });
182 }
183 String::new()
184}
185
186fn split_on_decimal_point(number: String) -> [String; 2] {
187 let mut v: [String; 2] = [String::new(), String::new()];
188 number
189 .split('.')
190 .into_iter()
191 .enumerate()
192 .for_each(|(idx, n)| v[idx] = n.to_owned());
193 v
194}
195
196fn capitalise_first_letter(mut word: String) -> String {
197 if word.is_empty() {
198 return "".to_owned();
199 }
200 word.remove(0).to_uppercase().to_string() + &word
201}
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use rstest::*;
206
207 #[rstest]
209 #[case(123.456, "123.46")] #[case(123.4567, "123.46")]
211 #[case(123.4, "123.40")] #[case(123.056, "123.06")]
213 #[case(123.006, "123.01")] #[case(123.005, "123.01")]
215 #[case(123.004, "123.00")] #[case(123.9999, "124.00")]
217 #[case(9_999_999_999_999.99999, "10000000000000.00")]
218 fn test_round_and_format_number(#[case] input: f64, #[case] expected: &str) {
219 assert_eq!(round_and_format_number(input), expected);
220 }
221
222 #[rstest]
223 #[case(0.099, true, "Zero and 10/100")] #[case(1.0, true, "One")] #[case(15.04, true, "Fifteen and 4/100")] #[case(99988389.123, true, "Ninety-nine million, \
228 nine hundred eighty-eight thousand, \
229 three hundred eighty-nine and 12/100"
230 )]
231 #[case(9308120381241.876, true, "Nine trillion, \
233 three hundred eight billion, \
234 one hundred twenty million, \
235 three hundred eighty-one thousand, \
236 two hundred forty-one and 88/100"
237 )]
238 #[case(9890984381241.55, true, "Nine trillion, \
240 eight hundred ninety billion, \
241 nine hundred eighty-four million, \
242 three hundred eighty-one thousand, \
243 two hundred forty-one and 55/100"
244 )]
245 #[case(9_999_999_999_999.0100, true,
247 "Nine trillion, \
248 nine hundred ninety-nine billion, \
249 nine hundred ninety-nine million, \
250 nine hundred ninety-nine thousand, \
251 nine hundred ninety-nine and 1/100"
252 )]
253 #[case(999_999_999_999.9999, true, "One trillion")] #[case(9_999_999_999_999.09999, true, "Nine trillion, nine hundred ninety-nine billion, \
256 nine hundred ninety-nine million, \
257 nine hundred ninety-nine thousand, \
258 nine hundred ninety-nine and 10/100"
259 )]
260 #[case(9_999_999_999_999.989, true, "Nine trillion, nine hundred ninety-nine billion, \
262 nine hundred ninety-nine million, \
263 nine hundred ninety-nine thousand, \
264 nine hundred ninety-nine and 99/100"
265 )]
266 #[case(9_999_999_999_999.99, true, "Nine trillion, \
268 nine hundred ninety-nine billion, \
269 nine hundred ninety-nine million, \
270 nine hundred ninety-nine thousand, \
271 nine hundred ninety-nine and 99/100"
272 )]
273 #[case(9_999_999_999_999.9999, true, "number too large"
275 )]
276 #[case(0.999_999_999_999_999_999_999_999_999_999_999_999_999_999_999_999_999, true, "One"
278 )]
279 #[case("222.22", true, "Two hundred twenty-two and 22/100")]
280 #[case("1.1e+6", true, "One million, one hundred thousand")]
281 #[case("-1.1e+6", true, "One million, one hundred thousand")]
282 #[case("10.0e+6", true, "Ten million")]
283
284 fn test_float_inputs(#[case] input: f64, #[case] capitalise: bool, #[case] expected: &str) {
285 assert_eq!(number_to_words(input, capitalise), expected);
286 }
287
288 #[rstest]
289 #[case(1, false, "one")]
290 #[case(15, false, "fifteen")]
291 #[case(1266, false, "one thousand, two hundred sixty-six")]
292 #[case(
293 1230812,
294 false,
295 "one million, \
296 two hundred thirty thousand, \
297 eight hundred twelve"
298 )]
299 #[case(
300 99988389,
301 false,
302 "ninety-nine million, \
303 nine hundred eighty-eight thousand, \
304 three hundred eighty-nine"
305 )]
306 fn test_signed_integer_inputs(
307 #[case] input: i32,
308 #[case] capitalise: bool,
309 #[case] expected: &str,
310 ) {
311 assert_eq!(number_to_words(input, capitalise), expected);
312 }
313
314 #[rstest]
315 #[case(1, true, "One")]
316 #[case(15, true, "Fifteen")]
317 #[case(
318 1266,
319 true,
320 "One thousand, \
321 two hundred sixty-six"
322 )]
323 #[case(
324 1230812,
325 true,
326 "One million, \
327 two hundred thirty thousand, \
328 eight hundred twelve"
329 )]
330 #[case(
331 99988389,
332 true,
333 "Ninety-nine million, \
334 nine hundred eighty-eight thousand, \
335 three hundred eighty-nine"
336 )]
337
338 fn test_unsigned_integer_inputs(
339 #[case] input: u32,
340 #[case] capitalise: bool,
341 #[case] expected: &str,
342 ) {
343 assert_eq!(number_to_words(input, capitalise), expected);
344 }
345
346 #[rstest]
348 #[case("0.0", ["0", "0"])]
349 #[case("0.00", ["0", "00"])]
350 #[case("1.0", ["1", "0"])]
351 #[case("1.1", ["1", "1"])]
352 #[case("99.999", ["99", "999"])] #[case("000.0", ["000", "0"])]
354 #[case("9999999999.99", ["9999999999", "99"])]
355 #[case("1.", ["1", ""])]
356 #[case(".", ["", ""])]
357 #[case("", ["", ""])] fn splitting_test(#[case] input: String, #[case] expected: [&str; 2]) {
359 assert_eq!(split_on_decimal_point(input), expected);
360 }
361
362 #[rstest]
364 #[case("one and", "One and")]
365 #[case("fifteen and 4/100", "Fifteen and 4/100")]
366 #[case("ninety", "Ninety")]
367 #[case("12345", "12345")]
368 #[case("tWELVE", "TWELVE")]
369 #[case("$banana", "$banana")]
370 #[case("", "")]
371
372 fn test_capitalisation(#[case] input: String, #[case] expected: String) {
373 assert_eq!(capitalise_first_letter(input), expected);
374 }
375}