use alloc::string::String;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
Empty,
InvalidNumber(String),
UnknownSuffix(String),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::Empty => f.write_str("empty input"),
ParseError::InvalidNumber(s) => write!(f, "invalid number: {s:?}"),
ParseError::UnknownSuffix(s) => write!(f, "unknown magnitude suffix: {s:?}"),
}
}
}
impl core::error::Error for ParseError {}
pub fn parse(s: &str) -> Result<f64, ParseError> {
let t = s.trim();
if t.is_empty() {
return Err(ParseError::Empty);
}
let (num_part, suffix_part) = split_number_suffix(t);
if !suffix_part.is_empty() && !suffix_part.chars().all(|c| c.is_ascii_alphabetic()) {
return Err(ParseError::InvalidNumber(String::from(t)));
}
let cleaned: String = num_part.chars().filter(|&c| c != ',' && c != '_').collect();
let base: f64 = cleaned
.parse()
.map_err(|_| ParseError::InvalidNumber(String::from(t)))?;
let mult = multiplier(suffix_part)
.ok_or_else(|| ParseError::UnknownSuffix(String::from(suffix_part)))?;
Ok(base * mult)
}
fn split_number_suffix(t: &str) -> (&str, &str) {
let bytes = t.as_bytes();
let mut i = 0;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let mut seen_dot = false;
let mut seen_exp = false;
while i < bytes.len() {
let c = bytes[i];
if c.is_ascii_digit() || c == b',' || c == b'_' {
i += 1;
} else if c == b'.' && !seen_dot && !seen_exp {
seen_dot = true;
i += 1;
} else if (c == b'e' || c == b'E') && !seen_exp {
let mut j = i + 1;
if j < bytes.len() && (bytes[j] == b'+' || bytes[j] == b'-') {
j += 1;
}
if j < bytes.len() && bytes[j].is_ascii_digit() {
seen_exp = true;
i = j;
} else {
break;
}
} else {
break;
}
}
(&t[..i], t[i..].trim())
}
fn multiplier(suffix: &str) -> Option<f64> {
if suffix.is_empty() {
return Some(1.0);
}
let s = suffix.to_ascii_lowercase();
let m = match s.as_str() {
"k" | "thousand" => 1e3,
"m" | "mn" | "million" => 1e6,
"b" | "bn" | "g" | "billion" => 1e9,
"t" | "trillion" => 1e12,
"p" | "quadrillion" => 1e15,
"quintillion" => 1e18,
"z" | "sextillion" => 1e21,
"y" | "septillion" => 1e24,
"r" | "octillion" => 1e27,
"q" | "nonillion" => 1e30,
"decillion" => 1e33,
_ => return None,
};
Some(m)
}