use num_traits::ToPrimitive;
pub(super) fn fixed_point(text: &str) -> Option<f64> {
const MANTISSA_DIGITS: u32 = 15;
const SCALE_DIGITS: u32 = 22;
let bytes = text.as_bytes();
let (negative, digits) = match bytes.split_first() {
Some((b'-', rest)) => (true, rest),
Some((b'+', rest)) => (false, rest),
_ => (false, bytes),
};
if digits.is_empty() {
return None;
}
let mut mantissa = 0_u64;
let mut count = 0_u32;
let mut decimals: Option<u32> = None;
for &byte in digits {
match byte {
b'0'..=b'9' => {
count += 1;
if count > MANTISSA_DIGITS {
return None;
}
mantissa = mantissa * 10 + u64::from(byte - b'0');
if let Some(places) = decimals.as_mut() {
*places += 1;
}
}
b'.' if decimals.is_none() => decimals = Some(0),
_ => return None,
}
}
let places = decimals?;
if count == 0 || places > SCALE_DIGITS {
return None;
}
let scale = 10_u64.checked_pow(places)?;
let value = exact(mantissa)? / exact(scale)?;
Some(if negative { -value } else { value })
}
fn exact(value: u64) -> Option<f64> {
const EXACT_LIMIT: u64 = 1_u64 << 53;
if value >= EXACT_LIMIT {
return None;
}
value.to_f64()
}
#[cfg(test)]
#[path = "number_tests.rs"]
mod tests;