1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// shortscale.rs
//
//! Converts numbers into English words.
//!
//! The [short scale](https://en.wikipedia.org/wiki/Long_and_short_scales#Comparison),
//! has different words for each power of 1000.
//!
//! This library expresses numbers from zero to thousands,
//! millions, billions, trillions, and quadrillions, up to 999_999_999_999_999_999.
//!
//! [github](https://github.com/jldec/shortscale-rs) | [crates.io](https://crates.io/crates/shortscale)
//!
//! Copyright 2021, Jürgen Leschner - github.com/jldec - MIT license

/// Returns String with words given an unsigned integer.
///
/// Supports positive integers from 0 to 999_999_999_999_999_999.  
/// Larger values return "(big number)".
///
/// # Example
/// ```
/// use shortscale::shortscale;
///
/// assert_eq!(
///     shortscale(420_000_999_015),
///     "four hundred and twenty billion nine hundred \
///     and ninety nine thousand and fifteen"
///     );
/// ```
pub fn shortscale(num: u64) -> String {
    let mut s = String::with_capacity(238);
    shortscale_string_writer(&mut s, num);
    return s;
}

/// Same as shortscale but writes words into mutable String.  
///
/// # Example
/// ```
/// use shortscale::shortscale_string_writer;
/// let mut my_string = String::from("The number 27 in words is ");
/// my_string.reserve(1024); // pre-allocate capacity (for performance only)
/// shortscale_string_writer(&mut my_string, 27);
/// assert_eq!(my_string, "The number 27 in words is twenty seven");
/// ```
pub fn shortscale_string_writer(s: &mut String, num: u64) {
    // simple lookup in map
    if num <= 20 || num > 999_999_999_999_999_999 {
        s.push_str(map(num));
        return;
    }
    let mut len: usize = 0; // mutated by push_words
    push_scale(s, &mut len, num, 1_000_000_000_000_000); // quadrillions
    push_scale(s, &mut len, num, 1_000_000_000_000); // trillions
    push_scale(s, &mut len, num, 1_000_000_000); // billions
    push_scale(s, &mut len, num, 1_000_000); // millions
    push_scale(s, &mut len, num, 1_000); // thousands
    push_hundreds(s, &mut len, num);
    let and_word: bool = len > 0;
    push_tens_and_units(s, &mut len, num, and_word);
}

fn push_word(s: &mut String, len: &mut usize, word: &str) {
    if *len > 0 {
        s.push_str(" ");
        *len += " ".len();
    }
    s.push_str(word);
    *len += word.len();
}

fn push_tens_and_units(s: &mut String, len: &mut usize, num: u64, and_word: bool) {
    let num = num % 100;
    if num == 0 {
        return;
    }
    if and_word {
        push_word(s, len, "and");
    }
    match num {
        1..=20 => push_word(s, len, map(num)),
        _ => {
            push_word(s, len, map(num / 10 * 10));
            let num = num % 10;
            match num {
                0 => (),
                _ => push_word(s, len, map(num)),
            };
        }
    };
}

fn push_hundreds(s: &mut String, len: &mut usize, num: u64) {
    let num = num / 100 % 10;
    if num == 0 {
        return;
    }
    push_word(s, len, map(num));
    push_word(s, len, map(100))
}

fn push_scale(s: &mut String, len: &mut usize, num: u64, thousands: u64) {
    let num = num / thousands % 1_000;
    if num == 0 {
        return;
    }
    push_hundreds(s, len, num);
    let and_word: bool = (num / 100 % 10) > 0;
    push_tens_and_units(s, len, num, and_word);
    push_word(s, len, map(thousands));
}

fn map(num: u64) -> &'static str {
    match num {
        0 => "zero",
        1 => "one",
        2 => "two",
        3 => "three",
        4 => "four",
        5 => "five",
        6 => "six",
        7 => "seven",
        8 => "eight",
        9 => "nine",
        10 => "ten",
        11 => "eleven",
        12 => "twelve",
        13 => "thirteen",
        14 => "fourteen",
        15 => "fifteen",
        16 => "sixteen",
        17 => "seventeen",
        18 => "eighteen",
        19 => "nineteen",
        20 => "twenty",
        30 => "thirty",
        40 => "fourty",
        50 => "fifty",
        60 => "sixty",
        70 => "seventy",
        80 => "eighty",
        90 => "ninety",
        100 => "hundred",
        1_000 => "thousand",
        1_000_000 => "million",
        1_000_000_000 => "billion",
        1_000_000_000_000 => "trillion",
        1_000_000_000_000_000 => "quadrillion",
        _ => "(big number)",
    }
}

#[cfg(any(extra, doc))]
pub mod extra;