rust_numerals/
lib.rs

1/// Represents the string "zero".
2const ZERO: &'static str = "zero";
3
4/// Represents the prefix for negative numbers.
5const SIGNED: &'static str = "minus ";
6
7/// Separator used to join various parts of the number's text representation.
8const SEPARATOR: &'static str = " and ";
9
10/// Representations for one-digit numbers.
11const ONES: [&'static str; 10] = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
12
13/// Representations for the numbers 11 to 19.
14const TEENS: [&'static str; 10] = ["", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"];
15
16/// Representations for multiples of ten.
17const TENS: [&'static str; 10] = ["", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];
18
19/// Represents the word for hundred.
20const HUNDRED: &'static str = "hundred";
21
22/// Representations for number scales.
23const SCALES: [&'static str; 7] = ["", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion"];
24
25/// Converts a given integer `number` to its cardinal string representation.
26///
27/// # Examples
28///
29/// ```
30/// use rust_numerals::number_to_cardinal;
31/// assert_eq!(number_to_cardinal(12345), "twelve thousand three hundred and forty-five");
32/// ```
33///
34/// # Arguments
35///
36/// * `number` - The integer value to be converted to text.
37///
38/// # Returns
39///
40/// Returns a `String` representing the cardinal version of the input number.
41pub fn number_to_cardinal(number: i64) -> String {
42    if number == 0 {
43        return ZERO.to_string();
44    }
45
46    let mut words = String::new();
47    let mut num = if number < 0 { -number } else { number } as u64; // Use absolute value
48
49    // Decompose number into chunks of 3 digits and convert to words
50    let mut scale_idx = 0;
51    while num > 0 {
52        if let Some(chunk) = get_chunk_words(num % 1_000, scale_idx) {
53            words.insert_str(0, &chunk);
54        }
55        num /= 1_000;
56        scale_idx += 1;
57    }
58
59    if number < 0 {
60        words.insert_str(0, SIGNED);
61    }
62    words.trim().to_string()
63}
64
65/// Converts a chunk of the number to its cardinal string representation and appends its scale.
66///
67/// # Arguments
68///
69/// * `chunk` - A part of the number to be converted, typically 3 digits long.
70/// * `scale_idx` - The index representing the scale (thousand, million, etc.) of the chunk.
71///
72/// # Returns
73///
74/// Returns an `Option<String>` where the `Some` variant contains the cardinal representation of the chunk and the `None` variant is returned for chunks that are zeros.
75fn get_chunk_words(chunk: u64, scale_idx: usize) -> Option<String> {
76    if chunk == 0 {
77        return None;
78    }
79    
80    let mut result = convert_number_to_cardinal(chunk as i64);
81    if scale_idx > 0 {
82        result.push_str(&format!(" {} ", SCALES[scale_idx]));
83    }
84    Some(result)
85}
86
87/// Converts a number (up to 999) to its cardinal string representation.
88///
89/// # Arguments
90///
91/// * `number` - The integer value (up to 999) to be converted to text.
92///
93/// # Returns
94///
95/// Returns a `String` representing the cardinal version of the input number.
96fn convert_number_to_cardinal(number: i64) -> String {
97    let mut words = String::new();
98
99    // Handle hundreds place
100    if number >= 100 {
101        words.push_str(&format!("{} {}", ONES[(number / 100) as usize], HUNDRED));
102        if number % 100 != 0 {
103            words.push_str(SEPARATOR);
104        }
105    }
106
107    let remainder = number % 100;
108
109    // Handle tens and ones places
110    match remainder {
111        11..=19 => words.push_str(TEENS[(remainder - 10) as usize]),
112        1..=9 => words.push_str(ONES[remainder as usize]),
113        10 => words.push_str(TENS[1]),
114        _ => {
115            if remainder >= 20 {
116                words.push_str(TENS[remainder as usize / 10]);
117                if remainder % 10 != 0 {
118                    words.push_str(&format!("-{}", ONES[(remainder % 10) as usize]));
119                }
120            }
121        }
122    }
123    words
124}
125
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn it_works_with_ones() {
133        assert_eq!(number_to_cardinal(0), "zero");
134        assert_eq!(number_to_cardinal(1), "one");
135        assert_eq!(number_to_cardinal(2), "two");
136        assert_eq!(number_to_cardinal(3), "three");
137        assert_eq!(number_to_cardinal(4), "four");
138        assert_eq!(number_to_cardinal(5), "five");
139        assert_eq!(number_to_cardinal(6), "six");
140        assert_eq!(number_to_cardinal(7), "seven");
141        assert_eq!(number_to_cardinal(8), "eight");
142        assert_eq!(number_to_cardinal(9), "nine");
143    }
144
145    #[test]
146    fn it_works_with_signed_ones() {
147        assert_eq!(number_to_cardinal(-1), "minus one");
148        assert_eq!(number_to_cardinal(-2), "minus two");
149        assert_eq!(number_to_cardinal(-3), "minus three");
150        assert_eq!(number_to_cardinal(-4), "minus four");
151        assert_eq!(number_to_cardinal(-5), "minus five");
152        assert_eq!(number_to_cardinal(-6), "minus six");
153        assert_eq!(number_to_cardinal(-7), "minus seven");
154        assert_eq!(number_to_cardinal(-8), "minus eight");
155        assert_eq!(number_to_cardinal(-9), "minus nine");
156        assert_eq!(number_to_cardinal(-10), "minus ten");
157    }
158
159    #[test]
160    fn it_works_with_teens() {
161        assert_eq!(number_to_cardinal(11), "eleven");
162        assert_eq!(number_to_cardinal(12), "twelve");
163        assert_eq!(number_to_cardinal(13), "thirteen");
164        assert_eq!(number_to_cardinal(14), "fourteen");
165        assert_eq!(number_to_cardinal(15), "fifteen");
166        assert_eq!(number_to_cardinal(16), "sixteen");
167        assert_eq!(number_to_cardinal(17), "seventeen");
168        assert_eq!(number_to_cardinal(18), "eighteen");
169        assert_eq!(number_to_cardinal(19), "nineteen");
170    }
171
172    #[test]
173    fn it_works_with_tens() {
174        assert_eq!(number_to_cardinal(10), "ten");
175        assert_eq!(number_to_cardinal(20), "twenty");
176        assert_eq!(number_to_cardinal(21), "twenty-one");
177        assert_eq!(number_to_cardinal(30), "thirty");
178        assert_eq!(number_to_cardinal(33), "thirty-three");
179        assert_eq!(number_to_cardinal(40), "forty");
180        assert_eq!(number_to_cardinal(50), "fifty");
181        assert_eq!(number_to_cardinal(60), "sixty");
182        assert_eq!(number_to_cardinal(70), "seventy");
183        assert_eq!(number_to_cardinal(80), "eighty");
184        assert_eq!(number_to_cardinal(90), "ninety");
185    }
186
187    #[test]
188    fn it_works_with_thousands() {
189        assert_eq!(number_to_cardinal(1_000), "one thousand");
190        assert_eq!(number_to_cardinal(1_001), "one thousand one");
191        assert_eq!(number_to_cardinal(1_100), "one thousand one hundred");
192        assert_eq!(number_to_cardinal(10_000), "ten thousand");
193        assert_eq!(number_to_cardinal(11_000), "eleven thousand");
194        assert_eq!(number_to_cardinal(11_011), "eleven thousand eleven");
195        assert_eq!(number_to_cardinal(21_025), "twenty-one thousand twenty-five");
196        assert_eq!(number_to_cardinal(99_999), "ninety-nine thousand nine hundred and ninety-nine");
197    }
198
199    #[test]
200    fn it_works_with_larger_scales() {
201        assert_eq!(number_to_cardinal(1_000_000), "one million");
202        assert_eq!(number_to_cardinal(1_000_001), "one million one");
203        assert_eq!(number_to_cardinal(1_001_000), "one million one thousand");
204        assert_eq!(number_to_cardinal(1_001_001), "one million one thousand one");
205        assert_eq!(number_to_cardinal(1_061_044), "one million sixty-one thousand forty-four");
206
207        assert_eq!(number_to_cardinal(1_000_000_000), "one billion");
208        assert_eq!(number_to_cardinal(1_000_000_001), "one billion one");
209        assert_eq!(number_to_cardinal(1_000_001_000), "one billion one thousand");
210        assert_eq!(number_to_cardinal(1_000_001_001), "one billion one thousand one");
211        assert_eq!(number_to_cardinal(1_000_456_501), "one billion four hundred and fifty-six thousand five hundred and one");
212
213        assert_eq!(number_to_cardinal(1_000_000_000_000), "one trillion");
214        assert_eq!(number_to_cardinal(1_000_000_000_000_000), "one quadrillion");
215        assert_eq!(number_to_cardinal(1_000_000_000_000_000_000), "one quintillion");
216
217        assert_eq!(number_to_cardinal(2_000_000_000_000), "two trillion");
218        assert_eq!(number_to_cardinal(2_000_000_000_000_000), "two quadrillion");
219        assert_eq!(number_to_cardinal(2_000_000_000_000_000_000), "two quintillion");
220        assert_eq!(number_to_cardinal(2_000_892_000_560_056_000), "two quintillion eight hundred and ninety-two trillion five hundred and sixty million fifty-six thousand");
221
222        assert_eq!(number_to_cardinal(1_001_000_000_000), "one trillion one billion");
223        assert_eq!(number_to_cardinal(1_000_001_000_000_000), "one quadrillion one billion");
224        assert_eq!(number_to_cardinal(1_000_000_001_000_000_000), "one quintillion one billion");
225        assert_eq!(number_to_cardinal(1_000_892_000_560_056_000), "one quintillion eight hundred and ninety-two trillion five hundred and sixty million fifty-six thousand");
226
227        assert_eq!(number_to_cardinal(1_001_000_000_001), "one trillion one billion one");
228        assert_eq!(number_to_cardinal(1_000_001_000_000_001), "one quadrillion one billion one");
229        assert_eq!(number_to_cardinal(1_000_000_001_000_000_001), "one quintillion one billion one");
230
231        assert_eq!(number_to_cardinal(1_100_100_100_100_100_101), "one quintillion one hundred quadrillion one hundred trillion one hundred billion one hundred million one hundred thousand one hundred and one");
232    }
233}