fn lt1000(n: u16, words: &mut Vec<String>) {
let hundreds = n / 100;
if hundreds != 0 {
lt100(hundreds as u8, words);
words.push("hundred".to_string());
}
let ones_and_tens = n % 100;
if ones_and_tens != 0 {
lt100(ones_and_tens as u8, words);
}
}
fn lt100(n: u8, words: &mut Vec<String>) {
const NUMS_SMALLER_THAN_20: [&str; 19] = [
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven",
"twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
];
const NUMS_SMALLER_THAN_20_OFFSET: usize = 1;
const MULTIPLES_OF_10: [&str; 8] = [
"twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety",
];
const MULTIPLES_OF_10_OFFSET: usize = 2;
if n < 20 {
words.push(NUMS_SMALLER_THAN_20[n as usize - NUMS_SMALLER_THAN_20_OFFSET].to_string());
}
else {
let tens = n / 10; let ones = n % 10;
let mut word = MULTIPLES_OF_10[tens as usize - MULTIPLES_OF_10_OFFSET].to_string();
if ones != 0 {
word += "-";
word += NUMS_SMALLER_THAN_20[ones as usize - NUMS_SMALLER_THAN_20_OFFSET];
}
words.push(word);
}
}
const PERIODS: [&str; 12] = [
"thousand", "million", "billion", "trillion", "quadrillion", "quintillion",
"sextillion", "septillion", "octillion", "nonillion", "decillion", "undecillion",
];
macro_rules! create_public_conversion_func_of_unsigned_int {
( $t:ty, $name:ident, $num_of_periods:literal ) => {
#[doc = concat!("`", stringify!($t), "`")]
#[doc = concat!("(`", stringify!($t), "`)")]
#[doc = concat!(
"# Example\n\
```\n\
use num2en::", stringify!($name), ";\n\n\
let number = 12_142;\n\
let words = ", stringify!($name), "(number);\n\
assert_eq!(words, \"twelve thousand one hundred forty-two\");\n\
```"
)]
pub fn $name(n: $t) -> String {
if n == 0 {
return "zero".to_string();
}
let mut words = Vec::<String>::new();
let mut divisor = (1000 as $t).pow($num_of_periods);
let mut idx = $num_of_periods;
while divisor >= 1000 {
idx -= 1;
let current_period = (n / divisor) % 1000;
if current_period != 0 {
lt1000(current_period as u16, &mut words);
words.push(PERIODS[idx].to_string());
}
divisor /= 1000;
}
lt1000((n % 1000) as u16, &mut words);
return words.join(" ");
}
};
}
#[cfg(target_pointer_width = "64")]
create_public_conversion_func_of_unsigned_int!(usize, usize_to_words, 6);
#[cfg(target_pointer_width = "32")]
create_public_conversion_func_of_unsigned_int!(usize, usize_to_words, 3);
create_public_conversion_func_of_unsigned_int!(u128, u128_to_words, 12);
create_public_conversion_func_of_unsigned_int!(u64, u64_to_words, 6);
create_public_conversion_func_of_unsigned_int!(u32, u32_to_words, 3);
create_public_conversion_func_of_unsigned_int!(u16, u16_to_words, 1);
pub fn u8_to_words(n: u8) -> String {
if n == 0 {
return "zero".to_string();
}
let mut words = Vec::<String>::new();
lt1000(n as u16, &mut words);
return words.join(" ");
}
const ORD_NUMS_EXCEPTIONS: [(&str, &str); 7] = [
("one", "first"), ("two", "second"), ("three", "third"), ("five", "fifth"),
("eight", "eighth"), ("nine", "ninth"), ("twelve", "twelfth"),
];
macro_rules! create_public_conversion_func_of_unsigned_int_ord {
( $t:ty, $name:ident, $num_of_periods:literal ) => {
#[doc = concat!("`", stringify!($t), "`")]
#[doc = concat!("(`", stringify!($t), "`)")]
#[doc = concat!(
"# Example\n\
```\n\
use num2en::", stringify!($name), ";\n\n\
let number = 12;\n\
let words = ", stringify!($name), "(number);\n\
assert_eq!(words, \"twelfth\");\n\n\
let number = 12_142;\n\
let words = ", stringify!($name), "(number);\n\
assert_eq!(words, \"twelve thousand one hundred forty-second\");\n\
```"
)]
pub fn $name(n: $t) -> String {
if n == 0 {
return "zeroth".to_string();
}
let mut words = Vec::<String>::new();
let mut divisor = (1000 as $t).pow($num_of_periods);
let mut idx = $num_of_periods;
while divisor >= 1000 {
idx -= 1;
let current_period = (n / divisor) % 1000;
if current_period != 0 {
lt1000(current_period as u16, &mut words);
words.push(PERIODS[idx].to_string());
}
divisor /= 1000;
}
lt1000((n % 1000) as u16, &mut words);
let mut last_word = &words.pop().unwrap()[..];
let mut penultimate_word = "";
if let Some(hyphen_index) = last_word.find('-') {
penultimate_word = &last_word[.. hyphen_index + 1];
last_word = &last_word[hyphen_index + 1 ..];
}
if let Some(index) = ORD_NUMS_EXCEPTIONS.iter().position(|x| x.0 == last_word) {
words.push(penultimate_word.to_string() + ORD_NUMS_EXCEPTIONS[index].1);
}
else if last_word.ends_with("y") {
words.push(penultimate_word.to_string() + &last_word[.. last_word.len() - 1] + "ieth");
}
else {
words.push(penultimate_word.to_string() + last_word + "th");
}
return words.join(" ");
}
};
}
#[cfg(target_pointer_width = "64")]
create_public_conversion_func_of_unsigned_int_ord!(usize, usize_to_ord_words, 6);
#[cfg(target_pointer_width = "32")]
create_public_conversion_func_of_unsigned_int_ord!(usize, usize_to_ord_words, 3);
create_public_conversion_func_of_unsigned_int_ord!(u128, u128_to_ord_words, 12);
create_public_conversion_func_of_unsigned_int_ord!(u64, u64_to_ord_words, 6);
create_public_conversion_func_of_unsigned_int_ord!(u32, u32_to_ord_words, 3);
create_public_conversion_func_of_unsigned_int_ord!(u16, u16_to_ord_words, 1);
pub fn u8_to_ord_words(n: u8) -> String { u16_to_ord_words(n as u16) }
macro_rules! create_public_conversion_func_of_signed_int {
( $t:tt, $name:ident, $num_of_periods:literal ) => {
#[doc = concat!("`", stringify!($t), "`")]
#[doc = concat!("(`", stringify!($t), "`)")]
#[doc = concat!(
"# Example\n\
```\n\
use num2en::", stringify!($name), ";\n\n\
let number = 1969;\n\
let words = ", stringify!($name), "(number);\n\
assert_eq!(words, \"one thousand nine hundred sixty-nine\");\n\n\
let number = -2918;\n\
let words = ", stringify!($name), "(number);\n\
assert_eq!(words, \"negative two thousand nine hundred eighteen\");\n\
```"
)]
pub fn $name(n: $t) -> String {
if n == 0 {
return "zero".to_string();
}
let mut words = Vec::<String>::new();
type UnsignedType = signed_to_unsigned!($t);
let mut nonnegative_n = n as UnsignedType;
if n < 0 {
words.push("negative".to_string());
if n > <$t>::MIN {
nonnegative_n = -n as UnsignedType;
}
}
let mut divisor = (1000 as UnsignedType).pow($num_of_periods);
let mut idx = $num_of_periods;
while divisor >= 1000 {
idx -= 1;
let current_period = (nonnegative_n / divisor) % 1000;
if current_period != 0 {
lt1000(current_period as u16, &mut words);
words.push(PERIODS[idx].to_string());
}
divisor /= 1000;
}
lt1000((nonnegative_n % 1000) as u16, &mut words);
return words.join(" ");
}
};
}
macro_rules! signed_to_unsigned {
(i16) => { u16 };
(i32) => { u32 };
(i64) => { u64 };
(i128) => { u128 };
(isize) => { usize };
}
#[cfg(target_pointer_width = "64")]
create_public_conversion_func_of_signed_int!(isize, isize_to_words, 6);
#[cfg(target_pointer_width = "32")]
create_public_conversion_func_of_signed_int!(isize, isize_to_words, 3);
create_public_conversion_func_of_signed_int!(i128, i128_to_words, 12);
create_public_conversion_func_of_signed_int!(i64, i64_to_words, 6);
create_public_conversion_func_of_signed_int!(i32, i32_to_words, 3);
create_public_conversion_func_of_signed_int!(i16, i16_to_words, 1);
pub fn i8_to_words(n: i8) -> String {
if n == 0 {
return "zero".to_string();
}
let mut words = Vec::<String>::new();
let mut nonnegative_n = n as u8;
if n < 0 {
words.push("negative".to_string());
if n > i8::MIN {
nonnegative_n = -n as u8;
}
}
lt1000(nonnegative_n as u16, &mut words);
return words.join(" ");
}
#[derive(Debug, PartialEq)]
pub enum DigitConversionError {
InvalidCharacter,
}
pub fn str_digits_to_words(digits: &str) -> Result<String, DigitConversionError> {
let mut words = Vec::with_capacity(digits.len());
for digit in digits.chars() {
words.push(match digit {
'0' => "zero",
'1' => "one",
'2' => "two",
'3' => "three",
'4' => "four",
'5' => "five",
'6' => "six",
'7' => "seven",
'8' => "eight",
'9' => "nine",
_ => return Err(DigitConversionError::InvalidCharacter)
});
}
Ok(words.join(" "))
}
#[derive(Debug, PartialEq)]
pub enum StrConversionError {
InvalidString,
TooLarge,
}
pub fn str_to_words(string: &str) -> Result<String, StrConversionError> {
use std::num::IntErrorKind;
if string.len() == 0 {
return Ok("".to_string());
}
let mut decimal_point_flag = false;
let mut at_least_one_digit_flag = false;
for (i, byte) in string.bytes().enumerate() {
if byte == b'.' {
if decimal_point_flag {
return Err(StrConversionError::InvalidString);
}
decimal_point_flag = true;
continue;
}
if byte >= b'0' && byte <= b'9' {
at_least_one_digit_flag = true;
}
else if !(i == 0 && byte == b'-') {
return Err(StrConversionError::InvalidString);
}
}
if !at_least_one_digit_flag {
return Err(StrConversionError::InvalidString)
}
let mut string = string;
let mut words = Vec::<String>::new();
if string.bytes().nth(0).unwrap() == b'-' {
words.push("negative".to_string());
string = &string[1..];
}
let floating_point_index_option = string.find('.');
let integer_part_result = string[..floating_point_index_option.unwrap_or(string.len())].parse::<u128>();
match integer_part_result {
Err(parse_int_err) => {
match parse_int_err.kind() {
IntErrorKind::Empty => {},
IntErrorKind::InvalidDigit => unreachable!(),
IntErrorKind::NegOverflow => unreachable!(),
IntErrorKind::PosOverflow => {
return Err(StrConversionError::TooLarge);
},
IntErrorKind::Zero => unreachable!(),
_ => unreachable!(),
}
},
Ok(integer_part) => {
words.push(u128_to_words(integer_part));
}
}
if let Some(floating_point_index) = floating_point_index_option {
words.push("point".to_string());
if floating_point_index < string.len() - 1 {
let decimal_part = &string[floating_point_index + 1..];
words.push(str_digits_to_words(decimal_part).unwrap());
}
}
return Ok(words.join(" "));
}
#[derive(Debug, PartialEq)]
pub enum FloatConversionError {
NotFinite,
TooLarge,
}
macro_rules! create_public_conversion_func_of_float {
( $t:ty, $name:ident ) => {
#[doc = concat!("`", stringify!($t), "`")]
#[doc = concat!("(`", stringify!($t), "`)")]
#[doc = concat!(
"# Examples\n\
```\n\
use num2en::", stringify!($name), ";\n\
# use num2en::FloatConversionError;\n\n\
let number = 123.123;\n\
let result = ", stringify!($name), "(number);\n\
assert_eq!(result, Ok(\"one hundred twenty-three point one two three\".to_string()));\n\n\
let number = 4e-5;\n\
let result = ", stringify!($name), "(number);\n\
assert_eq!(result, Ok(\"zero point zero zero zero zero four\".to_string()));\n\n\
let number = 34.000;\n\
let result = ", stringify!($name), "(number);\n\
assert_eq!(result, Ok(\"thirty-four\".to_string()));\n\n\
let infinity = ", stringify!($t), "::INFINITY;\n\
let result = ", stringify!($name), "(infinity);\n\
assert_eq!(result, Err(FloatConversionError::NotFinite));\n\n\
let not_a_number = ", stringify!($t), "::NAN;\n\
let result = ", stringify!($name), "(not_a_number);\n\
assert_eq!(result, Err(FloatConversionError::NotFinite));\n\
```"
)]
pub fn $name(float: $t) -> Result<String, FloatConversionError> {
if !float.is_finite() {
return Err(FloatConversionError::NotFinite);
}
let float_string = float.to_string();
match str_to_words(&float_string) {
Err(StrConversionError::TooLarge) => return Err(FloatConversionError::TooLarge),
Err(StrConversionError::InvalidString) => unreachable!(),
Ok(words) => return Ok(words),
}
}
};
}
create_public_conversion_func_of_float!(f32, f32_to_words);
create_public_conversion_func_of_float!(f64, f64_to_words);
#[cfg(test)]
mod tests;