use super::funejson::Value; /// Largest integer a JavaScript number holds exactly; beyond it the three /// languages disagree about what number was passed. const MAX_MAGNITUDE: i64 = 9007199254740991; const ONES: [&str; 20] = [ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", ]; const TENS: [&str; 10] = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]; /// Short scale, as used in the UK since 1974: a billion is 10^9. const SCALES: [&str; 6] = ["", "thousand", "million", "billion", "trillion", "quadrillion"]; fn below_hundred(n: i64) -> String { if n < 20 { return ONES[n as usize].to_string(); } let unit = n % 10; if unit == 0 { TENS[(n / 10) as usize].to_string() } else { format!("{}-{}", TENS[(n / 10) as usize], ONES[unit as usize]) } } /// One group of three digits, 1 to 999. fn group(n: i64) -> String { let hundreds = n / 100; let rest = n % 100; if hundreds == 0 { return below_hundred(rest); } let head = format!("{} hundred", ONES[hundreds as usize]); if rest == 0 { head } else { format!("{} and {}", head, below_hundred(rest)) } } /// Spell out a whole number in British English words, cheque style: /// 1234 is "one thousand two hundred and thirty-four". /// /// # Panics /// Panics if the magnitude is above 9007199254740991. pub fn integer_to_words(value: i64) -> String { if value > MAX_MAGNITUDE || value < -MAX_MAGNITUDE { panic!( "{} is out of range: integerToWords handles magnitudes up to {}", value, MAX_MAGNITUDE ); } if value == 0 { return "zero".to_string(); } // Groups of three, least significant first. let mut groups: Vec = Vec::new(); let mut remaining = value.abs(); while remaining > 0 { groups.push(remaining % 1000); remaining /= 1000; } let mut words: Vec = Vec::new(); for i in (0..groups.len()).rev() { let g = groups[i]; if g == 0 { continue; } let mut text = group(g); if i > 0 { text = format!("{} {}", text, SCALES[i]); } else if groups.len() > 1 && g < 100 { // British usage: "one thousand and one", "one million and fifty". // Only the final group gets it, and only without hundreds of its own. text = format!("and {}", text); } words.push(text); } let body = words.join(" "); if value < 0 { format!("minus {}", body) } else { body } } pub fn fune_vector(args: &[Value]) -> Value { // Refuse what the typed signature cannot hold, with the wording TypeScript // and Python use, rather than let the conversion below quietly change it. if let Value::Float(f) = args[0] { if f.fract() != 0.0 { panic!("integerToWords needs a whole number, received {}", f); } } Value::Str(integer_to_words(args[0].as_i64())) }