#[derive(Debug, Clone, Copy, Default)]
pub struct HexSignificand {
m: u64,
e2: i32,
sticky: bool,
}
impl HexSignificand {
pub fn new() -> Self {
Self::default()
}
pub fn push_digit(&mut self, digit: u8, fractional: bool) {
if self.m >> 60 == 0 {
self.m = (self.m << 4) | u64::from(digit);
if fractional {
self.e2 = self.e2.saturating_sub(4);
}
} else {
self.sticky |= digit != 0;
if !fractional {
self.e2 = self.e2.saturating_add(4);
}
}
}
pub fn apply_binary_exponent(&mut self, exp: i32) {
self.e2 = self.e2.saturating_add(exp);
}
pub fn to_f64(&self) -> (f64, bool) {
let m = self.m;
if m == 0 {
return (0.0, false);
}
let shift = m.leading_zeros();
let mm = m << shift;
let e = i64::from(self.e2) + 63 - i64::from(shift);
if e > 1023 {
return (f64::INFINITY, true);
}
let tiny = e < -1022;
let keep = if tiny { e + 1075 } else { 53 };
if keep <= 0 {
let round_up = keep == 0 && (mm != 1 << 63 || self.sticky);
return if round_up {
(pow2(-1074), true)
} else {
(0.0, true)
};
}
let drop = 64 - keep as u32; let kept = mm >> drop;
let rest = mm & ((1u64 << drop) - 1);
let half = 1u64 << (drop - 1);
let inexact = rest != 0 || self.sticky;
let round_up = rest > half || (rest == half && (self.sticky || kept & 1 == 1));
let value = (kept + u64::from(round_up)) as f64 * pow2((e - keep + 1) as i32);
let erange = value.is_infinite() || (tiny && inexact);
(value, erange)
}
}
fn pow2(k: i32) -> f64 {
debug_assert!((-1074..=1023).contains(&k));
if k >= -1022 {
f64::from_bits(((k + 1023) as u64) << 52)
} else {
f64::from_bits(1u64 << (k + 1074))
}
}
fn is_c_space(c: u8) -> bool {
matches!(c, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseDoubleError {
NoConversion,
Overflow,
Underflow,
Extraneous,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Erange {
No,
Over,
Under,
}
fn classify(v: f64, mantissa_nonzero: bool) -> Erange {
if v.is_infinite() {
Erange::Over
} else if v == 0.0 && mantissa_nonzero {
Erange::Under
} else if v != 0.0 && v.is_subnormal() {
Erange::Over
} else {
Erange::No
}
}
fn strtod(s: &str) -> (f64, usize, Erange) {
let b = s.as_bytes();
let mut i = 0;
while i < b.len() && is_c_space(b[i]) {
i += 1;
}
let sign_at = i;
let mut neg = false;
if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
neg = b[i] == b'-';
i += 1;
}
let num = i;
if num + 1 < b.len() && b[num] == b'0' && (b[num + 1] | 0x20) == b'x' {
let mut j = num + 2;
let mut sig = HexSignificand::new();
let mut digits = 0usize;
while j < b.len() && b[j].is_ascii_hexdigit() {
sig.push_digit(hex_val(b[j]), false);
digits += 1;
j += 1;
}
if j < b.len() && b[j] == b'.' {
let mut k = j + 1;
let mut frac = 0usize;
while k < b.len() && b[k].is_ascii_hexdigit() {
sig.push_digit(hex_val(b[k]), true);
frac += 1;
k += 1;
}
if digits > 0 || frac > 0 {
digits += frac;
j = k;
}
}
if digits == 0 {
return (if neg { -0.0 } else { 0.0 }, num + 1, Erange::No);
}
if j < b.len() && (b[j] | 0x20) == b'p' {
let mut k = j + 1;
let mut eneg = false;
if k < b.len() && (b[k] == b'+' || b[k] == b'-') {
eneg = b[k] == b'-';
k += 1;
}
let digits_at = k;
let mut e: i32 = 0;
while k < b.len() && b[k].is_ascii_digit() {
e = e.saturating_mul(10).saturating_add((b[k] - b'0') as i32);
k += 1;
}
if k > digits_at {
sig.apply_binary_exponent(if eneg { -e } else { e });
j = k;
}
}
let (mut v, erange) = sig.to_f64();
if neg {
v = -v;
}
let erange = if !erange {
Erange::No
} else if v == 0.0 {
Erange::Under
} else {
Erange::Over
};
return (v, j, erange);
}
let rest = &s[num..];
if starts_ci(rest, "infinity") {
return (inf(neg), num + 8, Erange::No);
}
if starts_ci(rest, "inf") {
return (inf(neg), num + 3, Erange::No);
}
if starts_ci(rest, "nan") {
let mut j = num + 3;
if j < b.len() && b[j] == b'(' {
let mut k = j + 1;
while k < b.len() && b[k] != b')' {
k += 1;
}
if k < b.len() {
j = k + 1;
}
}
return (f64::NAN, j, Erange::No);
}
let mut j = num;
let mut digits = 0usize;
let mut nonzero = false;
while j < b.len() && b[j].is_ascii_digit() {
nonzero |= b[j] != b'0';
digits += 1;
j += 1;
}
if j < b.len() && b[j] == b'.' {
let mut k = j + 1;
let mut frac = 0usize;
while k < b.len() && b[k].is_ascii_digit() {
nonzero |= b[k] != b'0';
frac += 1;
k += 1;
}
if digits > 0 || frac > 0 {
digits += frac;
j = k;
}
}
if digits == 0 {
return (0.0, 0, Erange::No);
}
let mut end = j;
if j < b.len() && (b[j] | 0x20) == b'e' {
let mut k = j + 1;
if k < b.len() && (b[k] == b'+' || b[k] == b'-') {
k += 1;
}
let digits_at = k;
while k < b.len() && b[k].is_ascii_digit() {
k += 1;
}
if k > digits_at {
end = k;
}
}
let v = s[sign_at..end].parse::<f64>().unwrap_or(f64::NAN);
let erange = classify(v, nonzero);
(v, end, erange)
}
fn hex_val(c: u8) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
_ => (c | 0x20) - b'a' + 10,
}
}
fn inf(neg: bool) -> f64 {
if neg {
f64::NEG_INFINITY
} else {
f64::INFINITY
}
}
fn starts_ci(s: &str, word: &str) -> bool {
s.len() >= word.len() && s.as_bytes()[..word.len()].eq_ignore_ascii_case(word.as_bytes())
}
pub fn epics_parse_double(s: &str) -> Result<f64, ParseDoubleError> {
let (v, used, erange) = strtod(s);
if used == 0 {
return Err(ParseDoubleError::NoConversion);
}
match erange {
Erange::Over => return Err(ParseDoubleError::Overflow),
Erange::Under => return Err(ParseDoubleError::Underflow),
Erange::No => {}
}
if !s.as_bytes()[used..].iter().all(|&c| is_c_space(c)) {
return Err(ParseDoubleError::Extraneous);
}
Ok(v)
}
pub fn epics_scan_double(s: &str) -> Option<f64> {
epics_parse_double(s).ok()
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(s: &str) -> (f64, bool) {
let b = s.as_bytes();
let mut sig = HexSignificand::new();
let mut i = 2; while i < b.len() && b[i].is_ascii_hexdigit() {
sig.push_digit(hex_val(b[i]), false);
i += 1;
}
if i < b.len() && b[i] == b'.' {
i += 1;
while i < b.len() && b[i].is_ascii_hexdigit() {
sig.push_digit(hex_val(b[i]), true);
i += 1;
}
}
if i < b.len() && (b[i] | 0x20) == b'p' {
sig.apply_binary_exponent(s[i + 1..].parse::<i32>().unwrap());
}
sig.to_f64()
}
fn hex_val(c: u8) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
_ => (c | 0x20) - b'a' + 10,
}
}
#[test]
fn matches_glibc_strtod_across_the_subnormal_boundary() {
let rows: &[(&str, u64, bool)] = &[
("0x1p-1074", 0x0000_0000_0000_0001, false),
("0x2p-1075", 0x0000_0000_0000_0001, false),
("0x1p-1073", 0x0000_0000_0000_0002, false),
("0x1p-1023", 0x0008_0000_0000_0000, false),
("0x1.8p-1075", 0x0000_0000_0000_0001, true),
("0x1.4p-1074", 0x0000_0000_0000_0001, true),
("0x1.cp-1074", 0x0000_0000_0000_0002, true),
("0x3p-1075", 0x0000_0000_0000_0002, true),
("0x1.0000000000001p-1074", 0x0000_0000_0000_0001, true),
("0x123456789abcdefp-1100", 0x0000_0000_48d1_59e2, true),
("0x1.fffffffffffffp-1023", 0x0010_0000_0000_0000, true),
("0x1p-1075", 0, true), ("0x1p-1076", 0, true),
("0x1p-2000", 0, true),
("0x1p-1022", 0x0010_0000_0000_0000, false),
("0x1p1023", 0x7fe0_0000_0000_0000, false),
("0x10", 0x4030_0000_0000_0000, false),
("0x1.8p1", 0x4008_0000_0000_0000, false),
("0x1p1024", 0x7ff0_0000_0000_0000, true),
("0x1.fffffffffffff8p1023", 0x7ff0_0000_0000_0000, true),
("0x0p0", 0, false),
("0x0p-5000", 0, false),
("0x1.00000000000008p0", 0x3ff0_0000_0000_0000, false),
("0x1.00000000000018p0", 0x3ff0_0000_0000_0002, false),
("0x1.0000000000000fp0", 0x3ff0_0000_0000_0001, false),
];
for &(text, bits, erange) in rows {
let (v, e) = hex(text);
assert_eq!(v.to_bits(), bits, "value of {text}");
assert_eq!(e, erange, "ERANGE of {text}");
}
}
}