use astro_float::{BigFloat, Consts, Error, Exponent, Radix, RoundingMode, Sign, WORD_BIT_SIZE};
#[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,
}
#[allow(clippy::disallowed_methods)]
pub(crate) fn to_decimal_digits(
x: &BigFloat,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<DecimalDigits, Error> {
let (sign, digits, exponent) = x.convert_to_radix(Radix::Dec, rm, cc)?;
Ok(DecimalDigits {
sign,
digits,
exponent,
})
}
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 mut cc = consts();
let d = to_decimal_digits(
&BigFloat::from_f64(-12.5, 64),
RoundingMode::ToEven,
&mut cc,
)
.expect("finite value");
assert_eq!(d.sign, Sign::Neg);
assert_eq!(d.exponent, 2);
assert_eq!(&d.digits[..3], &[1, 2, 5]);
assert!(d.digits[3..].iter().all(|&x| x == 0));
assert!(
to_decimal_digits(
&BigFloat::from_f64(f64::NAN, 64),
RoundingMode::ToEven,
&mut cc
)
.is_err()
);
}
#[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())
);
}
}