use core::fmt;
use core::str::FromStr;
use super::consts::POW10_2I;
use super::{DoubleDouble as Dd, from_u128, pow2, scale_down_tiny};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseDoubleDoubleError;
impl ParseDoubleDoubleError {
pub(crate) const fn new() -> Self {
Self
}
}
impl fmt::Display for ParseDoubleDoubleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid double-double literal")
}
}
impl std::error::Error for ParseDoubleDoubleError {}
const MAX_DIGITS: u32 = 38;
const MAX_EXACT_POW10: u32 = 22;
const POW10_F64: [f64; 23] = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
];
const OVERFLOW_EXPONENT: i64 = 308;
const UNDERFLOW_EXPONENT: i64 = 362;
const MAX_POW10: u32 = 308;
const FULL_PRECISION_MIN: f64 = f64::from_bits((1023 - 968) << 52);
const TINY_SCALE_EXPONENT: i32 = 128;
impl Dd {
pub fn from_decimal_str(s: &str) -> Result<Self, ParseDoubleDoubleError> {
parse_literal(s.trim().as_bytes())
}
}
impl FromStr for Dd {
type Err = ParseDoubleDoubleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_literal(s.as_bytes())
}
}
fn parse_literal(t: &[u8]) -> Result<Dd, ParseDoubleDoubleError> {
let (negative, body) = match t.split_first() {
Some((b'-', rest)) => (true, rest),
Some((b'+', rest)) => (false, rest),
_ => (false, t),
};
let magnitude = if body.eq_ignore_ascii_case(b"inf") || body.eq_ignore_ascii_case(b"infinity") {
Dd::INFINITY
} else if body.eq_ignore_ascii_case(b"nan") {
Dd::NAN
} else {
let (m, e) = parse_decimal(body).ok_or(ParseDoubleDoubleError::new())?;
from_decimal(m, e)
};
Ok(if negative { -magnitude } else { magnitude })
}
fn parse_decimal(s: &[u8]) -> Option<(u128, i64)> {
let mut m: u128 = 0;
let mut kept = 0;
let mut exp: i64 = 0;
let mut round_up = None; let mut any_digit = false;
let mut seen_point = false;
let mut i = 0;
while let Some(&c) = s.get(i) {
match c {
b'0'..=b'9' => {
any_digit = true;
let d = c - b'0';
if kept < MAX_DIGITS {
if m != 0 || d != 0 {
m = m * 10 + u128::from(d); kept += 1;
}
if seen_point {
exp -= 1;
}
} else {
round_up.get_or_insert(d >= 5);
if !seen_point {
exp += 1;
}
}
}
b'.' if !seen_point => seen_point = true,
b'e' | b'E' => break,
_ => return None,
}
i += 1;
}
if !any_digit {
return None;
}
if i < s.len() {
exp = exp.saturating_add(parse_exponent(&s[i + 1..])?);
}
if round_up == Some(true) {
m += 1; }
Some((m, exp))
}
fn parse_exponent(s: &[u8]) -> Option<i64> {
let (negative, digits) = match s.split_first() {
Some((b'-', rest)) => (true, rest),
Some((b'+', rest)) => (false, rest),
_ => (false, s),
};
if digits.is_empty() {
return None;
}
let mut e: i64 = 0;
for &c in digits {
if !c.is_ascii_digit() {
return None;
}
e = e.saturating_mul(10).saturating_add(i64::from(c - b'0'));
}
Some(if negative { -e } else { e })
}
fn from_decimal(mut m: u128, mut e: i64) -> Dd {
if m == 0 {
return Dd::ZERO;
}
while m % 10 == 0 {
m /= 10;
e = e.saturating_add(1);
}
if e >= 0 {
if e > OVERFLOW_EXPONENT {
return Dd::INFINITY;
}
let e = e as u32;
if let Some(n) = 10u128.checked_pow(e).and_then(|p| m.checked_mul(p)) {
return from_u128(n);
}
let v = mul_pow10(from_u128(m), e);
if v.hi.is_infinite() {
return mul_pow10_halved(m, e);
}
v
} else {
if e < -UNDERFLOW_EXPONENT {
return Dd::ZERO;
}
let k = e.unsigned_abs() as u32;
let v = div_pow10(from_u128(m), k);
if v.hi < FULL_PRECISION_MIN {
return tiny_quotient(m, k);
}
v
}
}
fn mul_pow10(v: Dd, e: u32) -> Dd {
if e <= MAX_EXACT_POW10 {
return v * POW10_F64[e as usize];
}
let mut v = v * POW10_F64[(e & 15) as usize];
for (i, p) in POW10_2I.iter().enumerate().skip(4) {
if e & (1 << i) != 0 {
v *= *p;
}
}
v
}
fn div_pow10(v: Dd, k: u32) -> Dd {
if k <= MAX_EXACT_POW10 {
v / POW10_F64[k as usize]
} else if k <= MAX_POW10 {
v / mul_pow10(Dd::ONE, k)
} else {
v / mul_pow10(Dd::ONE, k - 256) / POW10_2I[8]
}
}
#[cold]
#[inline(never)]
fn mul_pow10_halved(m: u128, e: u32) -> Dd {
mul_pow10(from_u128(m).mul_pow2(0.5), e).mul_pow2(2.0)
}
#[cold]
#[inline(never)]
fn tiny_quotient(m: u128, k: u32) -> Dd {
let v = div_pow10(from_u128(m).mul_pow2(pow2(TINY_SCALE_EXPONENT)), k);
scale_down_tiny(v, TINY_SCALE_EXPONENT)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn powers_of_ten_are_exact() {
for (k, &p) in POW10_F64.iter().enumerate() {
assert_eq!(p as u128, 10u128.pow(k as u32), "10^{k}");
}
}
}