use astro_float::{BigFloat, Consts, Error, Exponent, RoundingMode, Sign, WORD_BIT_SIZE};
use num_bigint::BigUint;
use num_traits::Zero;
#[inline]
pub(crate) fn pow(
b: &BigFloat,
e: &BigFloat,
wp: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> BigFloat {
if e.is_zero() {
return BigFloat::from_i32(1, wp);
}
if e.is_int()
&& let Some(ef) = to_f64(e)
&& ef.abs() < 1.0e9
{
let n = ef.abs() as usize;
let p = b.powi(n, wp, rm);
return if ef < 0.0 {
BigFloat::from_i32(1, wp).div(&p, wp, rm)
} else {
p
};
}
let ln_b = b.ln(wp + 32, rm, cc);
ln_b.mul(e, wp + 32, rm).exp(wp, rm, cc)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct DecimalDigits {
pub(crate) sign: Sign,
pub(crate) digits: Vec<u8>,
pub(crate) exponent: Exponent,
}
pub(crate) fn to_decimal_digits(x: &BigFloat) -> Result<DecimalDigits, Error> {
let Some((words, _, sign, exponent, _)) = x.as_raw_parts() else {
return Err(Error::InvalidArgument);
};
let mut m = BigUint::zero();
for &w in words.iter().rev() {
m = (m << WORD_BIT_SIZE) | BigUint::from(w);
}
if m.is_zero() {
return Ok(DecimalDigits {
sign,
digits: Vec::new(),
exponent: 0,
});
}
let p = words.len() * WORD_BIT_SIZE;
let n = (p as u64 * 301_029_996 / 1_000_000_000) as usize + 1;
let q = i64::from(exponent) - p as i64;
let (lead, scale, rest) = leading_decimal_digits(&m, q, n + 2, p);
let mut digits: Vec<u8> = lead.to_str_radix(10).bytes().map(|b| b - b'0').collect();
let mut dexp = scale + digits.len() as i64;
if digits.len() > n {
let next = digits[n];
let below = rest || digits[n + 1..].iter().any(|&d| d != 0);
let up = next > 5 || (next == 5 && (below || digits[n - 1] % 2 == 1));
digits.truncate(n);
if up {
let mut carried = true;
for d in digits.iter_mut().rev() {
if *d == 9 {
*d = 0;
} else {
*d += 1;
carried = false;
break;
}
}
if carried {
digits.insert(0, 1);
digits.truncate(n);
dexp += 1;
}
}
}
while digits.last() == Some(&0) {
digits.pop();
}
let exponent = Exponent::try_from(dexp).map_err(|_| Error::ExponentOverflow(sign))?;
Ok(DecimalDigits {
sign,
digits,
exponent,
})
}
const EXACT_DECIMAL_SCALE: i64 = 1 << 16;
fn leading_decimal_digits(m: &BigUint, q: i64, k: usize, p: usize) -> (BigUint, i64, bool) {
let b = m.bits() as i64;
let low_log10 =
crate::base::cast::f64_to_i64(((b + q - 1) as f64 * std::f64::consts::LOG10_2).floor())
.unwrap_or(0);
let s = low_log10 - k as i64 - 1;
let limit = EXACT_DECIMAL_SCALE.max(2 * p as i64);
if s.abs() <= limit {
return scaled_exact(m, q, s);
}
let mut bits = b as u64 + 4 * k as u64 + 64;
loop {
let (lo, hi, z) = pow5_bounds(s.unsigned_abs(), bits);
let (n_lo, n_hi) = if s > 0 {
let f = q - s - z;
(shifted_quotient(m, f, &hi), shifted_quotient(m, f, &lo))
} else {
let g = q - s + z;
(shift_floor(&(m * &lo), g), shift_floor(&(m * &hi), g))
};
if n_lo == n_hi || bits > MAX_DECIMAL_BITS {
return (n_lo, s, true);
}
bits *= 2;
}
}
const MAX_DECIMAL_BITS: u64 = 1 << 26;
fn scaled_exact(m: &BigUint, q: i64, s: i64) -> (BigUint, i64, bool) {
let five = BigUint::from(5u32).pow(s.unsigned_abs() as u32);
if s >= 0 {
let f = q - s;
let (num, den) = if f >= 0 {
(m << f.unsigned_abs(), five)
} else {
(m.clone(), five << f.unsigned_abs())
};
let n = &num / &den;
let rest = !(&num % &den).is_zero();
(n, s, rest)
} else {
let g = q - s;
let num = m * five;
if g >= 0 {
(num << g.unsigned_abs(), s, false)
} else {
let sh = g.unsigned_abs();
let rest = num.trailing_zeros().is_none_or(|tz| tz < sh);
(num >> sh, s, rest)
}
}
}
fn shift_floor(x: &BigUint, g: i64) -> BigUint {
if g >= 0 {
x << g.unsigned_abs()
} else {
x >> g.unsigned_abs()
}
}
fn shifted_quotient(m: &BigUint, f: i64, d: &BigUint) -> BigUint {
if f >= 0 {
(m << f.unsigned_abs()) / d
} else {
m / (d << f.unsigned_abs())
}
}
fn pow5_bounds(e: u64, bits: u64) -> (BigUint, BigUint, i64) {
let trim = |lo: BigUint, hi: BigUint, z: i64| {
let extra = hi.bits().saturating_sub(bits);
if extra == 0 {
(lo, hi, z)
} else {
(lo >> extra, (hi >> extra) + 1u32, z + extra as i64)
}
};
let one = || BigUint::from(1u32);
let (mut r_lo, mut r_hi, mut r_z) = (one(), one(), 0i64);
let (mut b_lo, mut b_hi, mut b_z) = (BigUint::from(5u32), BigUint::from(5u32), 0i64);
let mut e = e;
while e > 0 {
if e & 1 == 1 {
(r_lo, r_hi, r_z) = trim(&r_lo * &b_lo, &r_hi * &b_hi, r_z + b_z);
}
e >>= 1;
if e > 0 {
(b_lo, b_hi, b_z) = trim(&b_lo * &b_lo, &b_hi * &b_hi, 2 * b_z);
}
}
(r_lo, r_hi, r_z)
}
pub(crate) fn from_f64(x: f64, p: usize) -> BigFloat {
if x != 0.0 && x.abs() < f64::MIN_POSITIVE {
let scale = 2f64.powi(64);
return BigFloat::from_f64(x * scale, p).div(
&BigFloat::from_f64(scale, p),
p,
RoundingMode::ToEven,
);
}
BigFloat::from_f64(x, p)
}
pub(crate) fn is_strictly_positive(x: &BigFloat) -> bool {
!x.is_nan() && !x.is_zero() && !x.is_negative()
}
pub(crate) fn is_sign_positive(x: &BigFloat) -> bool {
!x.is_nan() && !x.is_negative()
}
pub(crate) fn to_f64_or_inf(x: &BigFloat) -> Option<f64> {
if x.is_nan() {
return None;
}
Some(to_f64(x).unwrap_or(if x.is_negative() {
f64::NEG_INFINITY
} else {
f64::INFINITY
}))
}
pub(crate) fn to_f64(x: &BigFloat) -> Option<f64> {
if x.is_zero() {
return Some(0.0);
}
let (words, _, sign, exponent, _) = x.as_raw_parts()?;
let signed = |v: f64| if sign == Sign::Neg { -v } else { v };
let mut acc: u128 = 0;
let mut filled = 0usize;
let mut sticky = false;
for &w in words.iter().rev() {
if filled < 64 {
acc = (acc << WORD_BIT_SIZE) | u128::from(w);
filled += WORD_BIT_SIZE;
} else if w != 0 {
sticky = true;
}
}
let top: u64 = if filled >= 64 {
let extra = filled - 64;
if extra > 0 && acc & ((1u128 << extra) - 1) != 0 {
sticky = true;
}
(acc >> extra) as u64
} else {
(acc << (64 - filled)) as u64
};
if top & (1 << 63) == 0 {
return Some(signed(0.0));
}
let mut unbiased = i64::from(exponent) - 1;
if unbiased > 1023 {
return None;
}
let keep = if unbiased >= -1022 {
53
} else {
unbiased + 1075
};
let (mant, above_half, exactly_half) = if keep <= 0 {
let at_half = top == 1 << 63 && !sticky;
(0u64, keep == 0 && !at_half, keep == 0 && at_half)
} else {
let shift = (64 - keep) as u32;
let mant = top >> shift;
let rem = top & ((1u64 << shift) - 1);
let half = 1u64 << (shift - 1);
(
mant,
rem > half || (rem == half && sticky),
rem == half && !sticky,
)
};
let round_up = above_half || (exactly_half && mant & 1 == 1);
let mut mant = mant + u64::from(round_up);
let bits = if keep == 53 {
if mant == 1 << 53 {
mant >>= 1;
unbiased += 1;
if unbiased > 1023 {
return None;
}
}
(((unbiased + 1023) as u64) << 52) | (mant & ((1 << 52) - 1))
} else {
mant
};
Some(signed(f64::from_bits(bits)))
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_MODES: [RoundingMode; 7] = [
RoundingMode::None,
RoundingMode::Up,
RoundingMode::Down,
RoundingMode::ToZero,
RoundingMode::FromZero,
RoundingMode::ToEven,
RoundingMode::ToOdd,
];
fn consts() -> Consts {
Consts::new().expect("astro-float constants cache")
}
#[test]
fn pow_terminates_on_exactly_representable_results() {
let mut cc = consts();
let p = 128;
let cases = [
(4.0, 0.5, 2.0),
(0.25, -0.5, 2.0),
(2.5, 2.0, 6.25),
(9.0, 1.5, 27.0),
];
for rm in ALL_MODES {
for (b, e, want) in cases {
let got = pow(
&BigFloat::from_f64(b, p),
&BigFloat::from_f64(e, p),
p,
rm,
&mut cc,
);
let err = got
.sub(&BigFloat::from_f64(want, p), p, RoundingMode::None)
.abs();
let tol = BigFloat::from_f64(want * 2f64.powi(-120), p);
assert!(err <= tol, "{b}^{e} under {rm:?} = {got}");
}
}
}
#[test]
fn pow_integer_and_zero_exponents() {
let mut cc = consts();
let p = 160;
let three = BigFloat::from_i32(3, p);
let r = pow(
&three,
&BigFloat::from_i32(5, p),
p,
RoundingMode::ToEven,
&mut cc,
);
assert_eq!(r, BigFloat::from_i32(243, p));
let r = pow(
&three,
&BigFloat::from_i32(0, p),
p,
RoundingMode::ToEven,
&mut cc,
);
assert_eq!(r, BigFloat::from_i32(1, p));
let r = pow(
&BigFloat::from_i32(2, p),
&BigFloat::from_i32(-3, p),
p,
RoundingMode::ToEven,
&mut cc,
);
assert_eq!(r, BigFloat::from_f64(0.125, p));
let r = pow(
&BigFloat::from_i32(-2, p),
&BigFloat::from_i32(3, p),
p,
RoundingMode::ToEven,
&mut cc,
);
assert_eq!(r, BigFloat::from_i32(-8, p));
}
#[test]
fn to_decimal_digits_reads_back() {
let d = to_decimal_digits(&BigFloat::from_f64(-12.5, 64)).expect("finite value");
assert_eq!(d.sign, Sign::Neg);
assert_eq!(d.exponent, 2);
assert_eq!(d.digits, vec![1, 2, 5]);
assert!(to_decimal_digits(&BigFloat::from_f64(f64::NAN, 64)).is_err());
assert!(to_decimal_digits(&BigFloat::from_f64(f64::INFINITY, 64)).is_err());
assert!(
to_decimal_digits(&BigFloat::new(64))
.expect("zero")
.digits
.is_empty()
);
let third =
BigFloat::from_i32(1, 64).div(&BigFloat::from_i32(3, 64), 64, RoundingMode::ToEven);
let d = to_decimal_digits(&third).expect("finite value");
assert_eq!(d.exponent, 0);
let mut want = vec![3; 19];
want.push(4);
assert_eq!(d.digits, want);
let two = BigFloat::from_i32(2, 128);
let tiny = BigFloat::from_i32(1, 128).div(
&two.powi(1100, 128, RoundingMode::ToEven),
128,
RoundingMode::ToEven,
);
let d = to_decimal_digits(&tiny).expect("finite value");
assert_eq!(d.exponent, -331);
assert_eq!(&d.digits[..12], &[7, 3, 6, 2, 1, 5, 1, 8, 2, 9, 0, 2]);
let big = two.powi(3000, 128, RoundingMode::ToEven);
let d = to_decimal_digits(&big).expect("finite value");
assert_eq!(d.exponent, 904);
assert_eq!(&d.digits[..12], &[1, 2, 3, 0, 2, 3, 1, 9, 2, 2, 1, 6]);
}
#[test]
fn bounded_scale_matches_exact_scale() {
let mut state = 0x9e37_79b9_7f4a_7c15u64;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for _ in 0..300 {
let m = BigUint::from(next() | (1 << 63)) << 64u32 | BigUint::from(next());
let q = (next() % 4001) as i64 - 2000;
let k = 20 + (next() % 30) as usize;
let b = m.bits() as i64;
let s = crate::base::cast::f64_to_i64(
((b + q - 1) as f64 * std::f64::consts::LOG10_2).floor(),
)
.unwrap_or(0)
- k as i64
- 1;
let exact = scaled_exact(&m, q, s);
let mut bits = b as u64 + 4 * k as u64 + 64;
let bounded = loop {
let (lo, hi, z) = pow5_bounds(s.unsigned_abs(), bits);
let (n_lo, n_hi) = if s > 0 {
let f = q - s - z;
(shifted_quotient(&m, f, &hi), shifted_quotient(&m, f, &lo))
} else {
let g = q - s + z;
(shift_floor(&(&m * &lo), g), shift_floor(&(&m * &hi), g))
};
if n_lo == n_hi {
break n_lo;
}
bits *= 2;
};
assert_eq!(exact.0, bounded, "m = {m}, q = {q}, s = {s}");
}
}
#[allow(clippy::disallowed_methods)]
fn library_digits(x: &BigFloat, cc: &mut Consts) -> Option<(Sign, Vec<u8>, Exponent)> {
x.convert_to_radix(astro_float::Radix::Dec, RoundingMode::ToEven, cc)
.ok()
}
#[test]
fn to_decimal_digits_matches_the_library() {
let cases: u64 = std::env::var("W11_DECIMAL_CASES")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(1500);
let mut cc = consts();
let mut state = 0x2545_f491_4f6c_dd1du64;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
let precisions = [
64usize, 128, 192, 256, 320, 512, 960, 1024, 1088, 2048, 4096,
];
let mut mismatches = 0u64;
let mut dump = std::env::var("W11_DECIMAL_DUMP")
.ok()
.and_then(|path| std::fs::File::create(path).ok());
for i in 0..cases {
let p = precisions[(next() % precisions.len() as u64) as usize];
let words = p / WORD_BIT_SIZE;
let mut m: Vec<astro_float::Word> =
(0..words).map(|_| next() as astro_float::Word).collect();
if let Some(top) = m.last_mut() {
*top |= 1 << (WORD_BIT_SIZE - 1);
}
if i % 7 == 0 {
let keep = 1 + (next() % (p as u64 - 1)) as usize;
for (j, w) in m.iter_mut().enumerate() {
let lo = j * WORD_BIT_SIZE;
if lo + WORD_BIT_SIZE <= p - keep {
*w = 0;
} else if lo < p - keep {
*w &= !((1 << (p - keep - lo)) - 1);
}
}
}
let e: i64 = match i % 3 {
0 => (next() % 401) as i64 - 200,
1 => (next() % 200_001) as i64 - 100_000,
_ => (next() % (1 << 31)) as i64 - (1 << 30),
};
let sign = if next() & 1 == 0 {
Sign::Pos
} else {
Sign::Neg
};
let x = BigFloat::from_raw_parts(&m, p, sign, e as Exponent, false);
let ours = to_decimal_digits(&x).expect("finite value");
if let Some(file) = dump.as_mut().filter(|_| i < 3000) {
use std::io::Write;
let mant: String = m.iter().rev().map(|w| format!("{w:016x}")).collect();
let digits: String = ours.digits.iter().map(|d| char::from(b'0' + d)).collect();
let neg = sign == Sign::Neg;
let _ = writeln!(file, "{p} {neg} {mant} {e} {digits} {}", ours.exponent);
}
let Some((s, d, ex)) = library_digits(&x, &mut cc) else {
continue;
};
if (s, &d, ex) != (ours.sign, &ours.digits, ours.exponent) {
mismatches += 1;
eprintln!(
"MISMATCH p={p} e={e} m={m:?} ours={:?}e{} lib={d:?}e{ex}",
ours.digits, ours.exponent
);
assert_eq!(p, 1024, "a disagreement away from the known library defect");
}
}
eprintln!("to_decimal_digits: {cases} cases, {mismatches} disagreements");
}
#[test]
fn to_f64_round_trips_and_rounds_to_even() {
for x in [1.0, -0.1, 1e300, -1e-300, f64::MAX, f64::MIN_POSITIVE] {
assert_eq!(to_f64(&BigFloat::from_f64(x, 64)), Some(x), "{x}");
assert_eq!(to_f64(&BigFloat::from_f64(x, 256)), Some(x), "{x}");
}
let p = 128;
let two = BigFloat::from_i32(2, p);
let pow2 = |k: usize| {
BigFloat::from_i32(1, p).div(
&two.powi(k, p, RoundingMode::ToEven),
p,
RoundingMode::ToEven,
)
};
assert_eq!(to_f64(&pow2(1074)), Some(5e-324));
assert_eq!(to_f64(&pow2(1070)), Some(0.5f64.powi(1070)));
let three_quarters = BigFloat::from_i32(3, p).mul(&pow2(1076), p, RoundingMode::ToEven);
assert_eq!(to_f64(&three_quarters), Some(5e-324));
assert_eq!(to_f64(&pow2(1075)), Some(0.0));
for x in [5e-324, 0.5f64.powi(1070)] {
assert_eq!(to_f64(&BigFloat::from_f64(x, 64)), Some(x / 2.0), "{x}");
}
assert_eq!(to_f64(&BigFloat::from_f64(0.0, 64)), Some(0.0));
assert_eq!(to_f64(&BigFloat::from_f64(f64::NAN, 64)), None);
assert_eq!(to_f64(&BigFloat::from_f64(f64::INFINITY, 64)), None);
assert_eq!(to_f64(&BigFloat::from_f64(f64::NEG_INFINITY, 64)), None);
let p = 128;
let one = BigFloat::from_i32(1, p);
let ulp_half = BigFloat::from_f64(2f64.powi(-53), p);
assert_eq!(
to_f64(&one.add(&ulp_half, p, RoundingMode::None)),
Some(1.0)
);
let odd = BigFloat::from_f64(1.0 + f64::EPSILON, p);
assert_eq!(
to_f64(&odd.add(&ulp_half, p, RoundingMode::None)),
Some(1.0 + 2.0 * f64::EPSILON)
);
let above = ulp_half.add(
&BigFloat::from_f64(2f64.powi(-100), p),
p,
RoundingMode::None,
);
assert_eq!(
to_f64(&one.add(&above, p, RoundingMode::None)),
Some(1.0 + f64::EPSILON)
);
let two = BigFloat::from_i32(2, p);
assert_eq!(to_f64(&two.powi(1024, p, RoundingMode::ToEven)), None);
let tiny = one.div(
&two.powi(1100, p, RoundingMode::ToEven),
p,
RoundingMode::ToEven,
);
assert_eq!(to_f64(&tiny), Some(0.0));
assert_eq!(
to_f64(&tiny.neg()).map(f64::to_bits),
Some((-0.0f64).to_bits())
);
}
#[test]
fn from_f64_keeps_subnormals() {
for x in [5e-324, -1e-310, 0.5f64.powi(1070), 2.2e-308, 1.0, -0.0, 0.0] {
assert_eq!(to_f64(&from_f64(x, 64)), Some(x), "{x}");
assert_eq!(to_f64(&from_f64(x, 192)), Some(x), "{x}");
}
assert_eq!(to_f64(&BigFloat::from_f64(5e-324, 64)), Some(0.0));
}
#[test]
fn strict_and_sign_positivity() {
let p = 64;
let zero = BigFloat::new(p);
assert!(!is_strictly_positive(&zero));
assert!(is_sign_positive(&zero));
assert!(!is_strictly_positive(&zero.neg()));
assert!(!is_sign_positive(&BigFloat::from_i32(-1, p)));
assert!(is_strictly_positive(&BigFloat::from_i32(1, p)));
assert!(is_strictly_positive(&BigFloat::from_f64(f64::INFINITY, p)));
let nan = BigFloat::from_f64(f64::NAN, p);
assert!(!is_strictly_positive(&nan) && !is_sign_positive(&nan));
assert_eq!(to_f64_or_inf(&nan), None);
let two = BigFloat::from_i32(2, p);
let huge = two.powi(5000, p, RoundingMode::ToEven);
assert_eq!(to_f64_or_inf(&huge), Some(f64::INFINITY));
assert_eq!(to_f64_or_inf(&huge.neg()), Some(f64::NEG_INFINITY));
assert_eq!(to_f64_or_inf(&two), Some(2.0));
}
}