#[inline]
fn char_letter_to_lower(c: u8) -> u8 {
c | 32
}
pub fn parse_int_with_radix_digits(
bytes: &[u8],
radix: u32,
allow_sep: bool,
mut digit: impl FnMut(u8),
) -> bool {
debug_assert!((2..=36).contains(&radix), "Invalid radix passed to parseIntWithRadix");
debug_assert!(!bytes.is_empty(), "Empty string");
let radix = radix as i32;
for (i, &c) in bytes.iter().enumerate() {
let c_low = char_letter_to_lower(c);
if c >= b'0' && c <= b'9' && (c as i32) < b'0' as i32 + radix {
digit(c - b'0');
} else if c_low >= b'a' && (c_low as i32) < b'a' as i32 + radix - 10 {
digit(c_low - b'a' + 0xa);
} else if allow_sep && c == b'_' {
if i == 0 || i == bytes.len() - 1 {
return false;
}
if bytes[i + 1] == b'_' {
return false;
}
} else {
return false;
}
}
true
}
pub fn parse_int_with_radix(bytes: &[u8], radix: u32, allow_sep: bool) -> Option<f64> {
let mut result: f64 = 0.0;
let success = parse_int_with_radix_digits(bytes, radix, allow_sep, |d| {
result *= radix as f64;
result += d as f64;
});
if !success {
return None;
}
const MAX_MANTISSA: f64 = 9007199254740992.0;
if result >= MAX_MANTISSA && radix.is_power_of_two() {
result = 0.0;
#[derive(PartialEq)]
enum Mode {
LeadingZero, Mantissa, ExpLowBit, ExpLeadingZero, Exponent, }
let mut remaining_mantissa: usize = 53;
let mut exp_factor: f64 = 0.0;
let mut cur_digit: usize = 0;
let mut last_mantissa_bit = false;
let mut lowest_exponent_bit = false;
let mut cur_mode = Mode::LeadingZero;
let mut itr = bytes.iter();
let mut bit_mask: u32 = 0;
loop {
if bit_mask == 0 {
match itr.next() {
None => break,
Some(&c) => {
let c = c as char;
if allow_sep && c == '_' {
continue;
}
let c_low = char_letter_to_lower(c as u8);
if c >= '0' && c <= '9' {
cur_digit = (c as u8 - b'0') as usize;
} else {
debug_assert!(
c_low >= b'a' && (c_low as i32) < b'a' as i32 + radix as i32 - 10
);
cur_digit = (c_low - b'a' + 0xa) as usize;
}
bit_mask = radix >> 1;
}
}
}
let cur_bit = (cur_digit as u32 & bit_mask) != 0;
bit_mask >>= 1;
match cur_mode {
Mode::LeadingZero => {
if cur_bit {
remaining_mantissa -= 1;
result = 1.0;
cur_mode = Mode::Mantissa;
}
}
Mode::Mantissa => {
result *= 2.0;
result += cur_bit as u8 as f64;
remaining_mantissa -= 1;
if remaining_mantissa == 0 {
last_mantissa_bit = cur_bit;
cur_mode = Mode::ExpLowBit;
}
}
Mode::ExpLowBit => {
lowest_exponent_bit = cur_bit;
exp_factor = 2.0;
cur_mode = Mode::ExpLeadingZero;
}
Mode::ExpLeadingZero => {
if cur_bit {
cur_mode = Mode::Exponent;
}
exp_factor *= 2.0;
}
Mode::Exponent => {
exp_factor *= 2.0;
}
}
}
match cur_mode {
Mode::LeadingZero | Mode::Mantissa | Mode::ExpLowBit => {
}
Mode::ExpLeadingZero => {
result += (lowest_exponent_bit && last_mantissa_bit) as u8 as f64;
result *= exp_factor;
}
Mode::Exponent => {
result += lowest_exponent_bit as u8 as f64;
result *= exp_factor;
}
}
}
Some(result)
}
pub fn str_to_double(bytes: &[u8]) -> Option<f64> {
let s = std::str::from_utf8(bytes).ok()?;
s.parse::<f64>().ok()
}
#[cfg(test)]
mod int_tests {
use super::*;
#[test]
fn small_exact() {
assert_eq!(parse_int_with_radix(b"ff", 16, true), Some(255.0));
assert_eq!(parse_int_with_radix(b"777", 8, true), Some(511.0));
assert_eq!(parse_int_with_radix(b"1010", 2, true), Some(10.0));
assert_eq!(parse_int_with_radix(b"123", 10, true), Some(123.0));
assert_eq!(parse_int_with_radix(b"z", 36, true), Some(35.0));
assert_eq!(parse_int_with_radix(b"a", 10, true), None);
assert_eq!(parse_int_with_radix(b"8", 8, true), None);
}
#[test]
fn separators() {
assert_eq!(parse_int_with_radix(b"1_000", 10, true), Some(1000.0));
assert_eq!(
parse_int_with_radix(b"dead_beef", 16, true),
Some(0xdeadbeef_u32 as f64)
);
assert_eq!(parse_int_with_radix(b"_1", 10, true), None); assert_eq!(parse_int_with_radix(b"1_", 10, true), None); assert_eq!(parse_int_with_radix(b"1__2", 10, true), None); assert_eq!(parse_int_with_radix(b"1_0", 10, false), None);
}
#[test]
fn invalid() {
assert_eq!(parse_int_with_radix(b"xyz", 16, true), None);
assert_eq!(parse_int_with_radix(b"12.3", 10, true), None);
}
#[test]
fn large_power_of_two_rounding_matches_u128_oracle() {
let cases: &[(&[u8], u32)] = &[
(b"20000000000001", 16), (b"1fffffffffffff", 16), (b"ffffffffffffffff", 16), (b"123456789abcdef0123", 16), (b"777777777777777777777", 8), (b"1111111111111111111111111111111111111111111111111111111", 2),
(b"20000000000000", 16), (b"33333333333333333333333333333", 4), (b"vvvvvvvvvvvv", 32), ];
for &(s, radix) in cases {
let txt = std::str::from_utf8(s).unwrap();
let expected = u128::from_str_radix(txt, radix).unwrap() as f64;
assert_eq!(
parse_int_with_radix(s, radix, true),
Some(expected),
"mismatch for {txt} radix {radix}"
);
}
}
#[test]
fn large_decimal() {
assert_eq!(
parse_int_with_radix(b"9007199254740993", 10, true),
Some(9007199254740993u128 as f64)
);
}
}
#[cfg(test)]
mod double_tests {
use super::*;
fn bits(v: f64) -> u64 {
v.to_bits()
}
#[test]
fn known_bit_patterns() {
assert_eq!(str_to_double(b"5").map(bits), Some(0x4014000000000000));
assert_eq!(str_to_double(b"0.1").map(bits), Some(0x3fb999999999999a));
assert_eq!(str_to_double(b"255").map(bits), Some(0x406fe00000000000));
assert_eq!(str_to_double(b"1e10").map(bits), Some(1e10f64.to_bits()));
assert_eq!(str_to_double(b"12.5").map(bits), Some(12.5f64.to_bits()));
}
#[test]
fn must_consume_all() {
assert_eq!(str_to_double(b"12x"), None);
assert_eq!(str_to_double(b""), None);
assert_eq!(str_to_double(b"1.2.3"), None);
}
#[test]
fn leading_plus_and_exponent() {
assert_eq!(str_to_double(b"+5").map(bits), Some(5.0f64.to_bits()));
assert_eq!(str_to_double(b"5e+3").map(bits), Some(5000.0f64.to_bits()));
assert_eq!(str_to_double(b"5E-3").map(bits), Some(0.005f64.to_bits()));
}
#[test]
fn out_of_range() {
assert_eq!(str_to_double(b"1e400"), Some(f64::INFINITY));
assert_eq!(str_to_double(b"1e-400"), Some(0.0));
}
}