use crate::{BETWEEN_CHECKS, error::Error, interrupt::check};
use num_integer::Integer;
use num_rational::Ratio;
use num_traits::{One, Signed, ToPrimitive, Zero};
pub fn residue<T: Clone + Integer>(x: &Ratio<T>, y: &Ratio<T>) -> Ratio<T> { if x.is_zero() { y.clone() } else { y - x * (y / x).floor() } }
pub(crate) fn boolean(value: bool) -> num_rational::BigRational { Ratio::from_integer(i64::from(value).into()) }
pub fn divide(x: &num_rational::BigRational, y: &num_rational::BigRational, mathish: bool) -> Result<Option<num_rational::BigRational>, Error> {
if y.is_zero() { return Ok((mathish && x.is_zero()).then(num_rational::BigRational::zero)); }
Ok(Some(x / y))
}
pub fn floor_divide(x: &num_rational::BigRational, y: &num_rational::BigRational, mathish: bool) -> Result<Option<num_rational::BigRational>, Error> {
Ok(divide(x, y, mathish)?.map(|value| value.floor()))
}
pub fn gcd<T: Clone + Integer>(x: &Ratio<T>, y: &Ratio<T>) -> Ratio<T> { Ratio::new(x.numer().gcd(y.numer()), x.denom().lcm(y.denom())) }
pub fn lcm<T: Clone + Integer>(x: &Ratio<T>, y: &Ratio<T>) -> Ratio<T> { let g = gcd(x, y); if g.is_zero() { g } else { x / &g * y } }
pub fn binom<T>(n: &Ratio<T>, k: i64) -> Result<Ratio<T>, Error> where T: Clone + Integer + Signed + ToPrimitive + From<i64> {
let whole = n.is_integer().then(|| n.to_integer());
if k < 0 {
let Some(n) = whole else { return Ok(Ratio::zero()) };
if !n.is_negative() || T::from(k) > n { return Ok(Ratio::zero()); }
let count = (n - T::from(k)).to_i64().expect("n - k lies below -k");
let c = binom(&Ratio::from_integer(T::from(-(k + 1))), count)?;
return Ok(if count % 2 == 0 { c } else { -c });
}
let k = match whole {
Some(n) if !n.is_negative() => {
if T::from(k) > n { return Ok(Ratio::zero()); }
let rest = n - T::from(k);
if rest < T::from(k) { rest.to_i64().expect("n - k lies below k") } else { k }
}
_ => k,
};
let mut value = Ratio::one();
for i in 0..k {
if i % BETWEEN_CHECKS as i64 == 0 { check()?; }
value = value * (n - Ratio::from_integer(T::from(i))) / Ratio::from_integer(T::from(i + 1));
}
Ok(value)
}
pub fn factorial(value: &num_rational::BigRational) -> Result<Option<num_rational::BigRational>, Error> {
if !value.is_integer() || value.is_negative() { return Ok(None); }
let n = value.to_u64().filter(|&n| n <= 100_000).ok_or_else(|| Error::new(crate::error::Kind::Limit, "exact factorial argument is too large"))?;
Ok(Some(Ratio::from_integer(crate::number_theory::factorial(n)?.into())))
}
pub fn power(x: &num_rational::BigRational, y: &num_rational::BigRational) -> Result<Option<num_rational::BigRational>, Error> {
if !y.is_integer() { return Ok(None); }
let n = y.to_i32().filter(|&n| n.unsigned_abs() <= 1_000_000).ok_or_else(|| Error::new(crate::error::Kind::Limit, "exact exponent is too large"))?;
if x.is_zero() && n < 0 { return Ok(None); }
Ok(Some(x.pow(n)))
}