use num_bigint::{BigInt, BigUint};
use num_rational::Ratio;
use num_traits::{One, Signed, Zero};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedSqrtRational {
sign: i8,
radicand: Ratio<BigInt>,
}
impl SignedSqrtRational {
pub fn zero() -> Self {
SignedSqrtRational {
sign: 0,
radicand: Ratio::zero(),
}
}
pub fn from_prefactor_radical(s: Ratio<BigInt>, d: Ratio<BigInt>) -> Self {
if s.is_zero() || d.is_zero() {
return Self::zero();
}
let sign: i8 = if s.is_negative() { -1 } else { 1 };
let d = if d.is_negative() { -d } else { d };
let radicand = (&s * &s) * d;
SignedSqrtRational { sign, radicand }
}
pub fn sign(&self) -> i8 {
self.sign
}
pub fn radicand(&self) -> &Ratio<BigInt> {
&self.radicand
}
pub fn signed_square(&self) -> Ratio<BigInt> {
match self.sign {
0 => Ratio::zero(),
1 => self.radicand.clone(),
_ => -self.radicand.clone(),
}
}
pub fn scale_int(mut self, k: i64) -> Self {
if k == 0 || self.sign == 0 {
return Self::zero();
}
if k < 0 {
self.sign = -self.sign;
}
let k2 = BigInt::from(k) * BigInt::from(k);
self.radicand *= Ratio::from(k2);
self
}
pub fn times_sqrt_int(mut self, n: u64) -> Self {
if n == 0 || self.sign == 0 {
return Self::zero();
}
self.radicand *= Ratio::from(BigInt::from(n));
self
}
pub fn neg_value(mut self) -> Self {
self.sign = -self.sign;
self
}
pub fn to_f64(&self) -> f64 {
if self.sign == 0 {
return 0.0;
}
let n = self.radicand.numer();
let d = self.radicand.denom();
let n = n.magnitude(); let d = d.magnitude();
let mag = sqrt_ratio_to_f64(n, d);
if self.sign < 0 {
-mag
} else {
mag
}
}
}
impl std::ops::Mul for SignedSqrtRational {
type Output = SignedSqrtRational;
fn mul(self, other: SignedSqrtRational) -> SignedSqrtRational {
if self.sign == 0 || other.sign == 0 {
return Self::zero();
}
SignedSqrtRational {
sign: self.sign * other.sign,
radicand: self.radicand * other.radicand,
}
}
}
fn sqrt_ratio_to_f64(n: &BigUint, d: &BigUint) -> f64 {
if n.is_zero() {
return 0.0;
}
let nb = n.bits() as i64;
let db = d.bits() as i64;
let mut g: i64 = 54 - (nb - db) / 2;
let (q, exp) = loop {
let two_g = 2 * g;
let scaled = if two_g >= 0 {
(n << (two_g as u64)) / d
} else {
n / (d << ((-two_g) as u64))
};
if scaled.is_zero() {
g += 8;
continue;
}
let q = scaled.sqrt();
let bits = q.bits() as i64;
if bits < 54 {
g += (54 - bits + 1) / 2 + 1;
continue;
}
if bits > 56 {
g -= (bits - 56 + 1) / 2 + 1;
continue;
}
break (q, g);
};
let bits = q.bits() as i64;
let drop = bits - 53; let mant: BigUint = &q >> (drop as u64);
let value_exp = drop - exp;
let two_m1 = (&mant << 1u32) + BigUint::one();
let mid_sq_int = &two_m1 * &two_m1; let p = 2 * value_exp - 2;
let (lhs, rhs) = if p >= 0 {
(n.clone(), (&mid_sq_int * d) << (p as u64))
} else {
(n << ((-p) as u64), &mid_sq_int * d)
};
let chosen: BigUint = match lhs.cmp(&rhs) {
std::cmp::Ordering::Less => mant, std::cmp::Ordering::Greater => mant + BigUint::one(), std::cmp::Ordering::Equal => {
if (&mant & BigUint::one()).is_zero() {
mant
} else {
mant + BigUint::one()
}
}
};
let mant_f = biguint_to_f64_exact(&chosen);
scale_pow2(mant_f, value_exp)
}
fn biguint_to_f64_exact(x: &BigUint) -> f64 {
let digits = x.to_u64_digits();
match digits.as_slice() {
[] => 0.0,
[lo] => *lo as f64,
_ => f64::INFINITY,
}
}
fn scale_pow2(x: f64, exp: i64) -> f64 {
let mut r = x;
let mut e = exp;
while e > 1023 {
r *= f64::from_bits(0x7FEu64 << 52); e -= 1023;
}
while e < -1022 {
r *= f64::from_bits(1u64 << 52); e += 1022;
}
r * two_pow_i(e)
}
fn two_pow_i(e: i64) -> f64 {
let biased = (e + 1023) as u64;
f64::from_bits(biased << 52)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
thread_local! {
static FACT: RefCell<Vec<BigInt>> = RefCell::new(vec![BigInt::one()]);
}
fn factorial(n: u64) -> BigInt {
FACT.with(|cell| {
let mut table = cell.borrow_mut();
while (table.len() as u64) <= n {
let len = table.len();
let next = &table[len - 1] * BigInt::from(len as u64);
table.push(next);
}
table[n as usize].clone()
})
}
fn ssr(s: i64, num: i64, den: i64) -> SignedSqrtRational {
SignedSqrtRational::from_prefactor_radical(
Ratio::new(BigInt::from(s), BigInt::from(1)),
Ratio::new(BigInt::from(num), BigInt::from(den)),
)
}
#[test]
fn zero_is_canonical() {
let z = SignedSqrtRational::zero();
assert_eq!(z.sign(), 0);
assert_eq!(z.to_f64(), 0.0);
let also_zero =
SignedSqrtRational::from_prefactor_radical(Ratio::zero(), Ratio::from(BigInt::from(5)));
assert_eq!(z, also_zero);
}
#[test]
fn sign_and_signed_square() {
let v = ssr(-1, 3, 4);
assert_eq!(v.sign(), -1);
assert_eq!(*v.radicand(), Ratio::new(BigInt::from(3), BigInt::from(4)));
assert_eq!(
v.signed_square(),
Ratio::new(BigInt::from(-3), BigInt::from(4))
);
}
#[test]
fn prefactor_folds_into_radicand() {
let v = SignedSqrtRational::from_prefactor_radical(
Ratio::new(BigInt::from(2), BigInt::from(3)),
Ratio::from(BigInt::from(5)),
);
assert_eq!(v.sign(), 1);
assert_eq!(*v.radicand(), Ratio::new(BigInt::from(20), BigInt::from(9)));
}
#[test]
fn product_multiplies_radicands_and_signs() {
let a = ssr(-1, 2, 1);
let b = ssr(1, 8, 1);
let p = a * b;
assert_eq!(p.sign(), -1);
assert_eq!(*p.radicand(), Ratio::from(BigInt::from(16)));
assert!((p.to_f64() - (-4.0)).abs() < 1e-12);
}
#[test]
fn scale_int_signs_and_squares() {
let v = ssr(1, 3, 1).scale_int(-2);
assert_eq!(v.sign(), -1);
assert_eq!(*v.radicand(), Ratio::from(BigInt::from(12)));
}
#[test]
fn to_f64_known_values() {
assert!((ssr(1, 1, 4).to_f64() - 0.5).abs() == 0.0);
assert!((ssr(1, 9, 1).to_f64() - 3.0).abs() == 0.0);
assert!((ssr(-1, 1, 6).to_f64() - (-(1.0f64 / 6.0).sqrt())).abs() <= f64::EPSILON);
let got = ssr(1, 1, 6).to_f64();
let want = (1.0f64 / 6.0).sqrt();
assert!((got - want).abs() <= (want * f64::EPSILON));
}
#[test]
fn to_f64_correctly_rounded_against_exact() {
for num in 1u64..60 {
for den in 1u64..60 {
let got = SignedSqrtRational::from_prefactor_radical(
Ratio::one(),
Ratio::new(BigInt::from(num), BigInt::from(den)),
)
.to_f64();
let approx = (num as f64 / den as f64).sqrt();
let ulp = approx.abs() * f64::EPSILON;
assert!(
(got - approx).abs() <= ulp + f64::MIN_POSITIVE,
"num={num} den={den} got={got} approx={approx}"
);
}
}
}
#[test]
fn to_f64_big_radicand() {
let big: BigInt = BigInt::from(2u64).pow(200) * 3;
let v = SignedSqrtRational::from_prefactor_radical(Ratio::one(), Ratio::from(big.clone()));
let got = v.to_f64();
let want = (3.0f64).sqrt() * 2f64.powi(100);
assert!(
(got - want).abs() <= want * 4.0 * f64::EPSILON,
"got={got} want={want}"
);
}
#[test]
fn factorial_table_grows() {
assert_eq!(factorial(0), BigInt::one());
assert_eq!(factorial(5), BigInt::from(120));
assert_eq!(factorial(10), BigInt::from(3_628_800));
assert_eq!(factorial(3), BigInt::from(6));
}
}