use crate::{IBig, RBig, UBig};
use oxinum_core::{OxiNumError, OxiNumResult};
pub fn continued_fraction(x: &RBig) -> Vec<IBig> {
let mut coeffs = Vec::new();
let mut num = x.numerator().clone();
let mut den: IBig = x.denominator().clone().into();
if den == IBig::ZERO {
return coeffs;
}
loop {
let (q, r) = div_floor(&num, &den);
coeffs.push(q);
if r == IBig::ZERO {
break;
}
num = den;
den = r;
}
coeffs
}
pub fn from_continued_fraction(coeffs: &[IBig]) -> OxiNumResult<RBig> {
if coeffs.is_empty() {
return Err(OxiNumError::Parse("empty continued fraction".into()));
}
let mut result = RBig::from(coeffs[coeffs.len() - 1].clone());
for coeff in coeffs[..coeffs.len() - 1].iter().rev() {
if result == RBig::ZERO {
return Err(OxiNumError::DivByZero);
}
let reciprocal = rational_reciprocal(&result)?;
result = RBig::from(coeff.clone()) + reciprocal;
}
Ok(result)
}
pub fn best_rational_approximation(x: &RBig, max_denom: &UBig) -> RBig {
if *max_denom == UBig::ZERO {
return RBig::from(x.floor());
}
let coeffs = continued_fraction(x);
if coeffs.is_empty() {
return RBig::ZERO;
}
let mut h_prev2 = IBig::ZERO; let mut h_prev1 = IBig::ONE; let mut k_prev2 = IBig::ONE; let mut k_prev1 = IBig::ZERO;
let max_denom_i: IBig = max_denom.clone().into();
let mut best = RBig::from(coeffs[0].clone());
for coeff in &coeffs {
let h_n = coeff * &h_prev1 + &h_prev2;
let k_n = coeff * &k_prev1 + &k_prev2;
if k_n > max_denom_i {
break;
}
best = rbig_from_signed(&h_n, &k_n);
h_prev2 = h_prev1;
h_prev1 = h_n;
k_prev2 = k_prev1;
k_prev1 = k_n;
}
best
}
pub fn to_decimal_string(x: &RBig, decimal_places: usize) -> String {
let num = x.numerator().clone();
let den: IBig = x.denominator().clone().into();
let is_negative = num < IBig::ZERO;
let abs_num = if is_negative { -num } else { num };
let (int_part, mut remainder) = div_floor(&abs_num, &den);
let mut result = String::new();
if is_negative && (int_part != IBig::ZERO || decimal_places > 0) {
result.push('-');
}
result.push_str(&int_part.to_string());
if decimal_places > 0 {
result.push('.');
let ten = IBig::from(10);
for _ in 0..decimal_places {
remainder *= &ten;
let (digit, new_rem) = div_floor(&remainder, &den);
result.push_str(&digit.to_string());
remainder = new_rem;
}
}
result
}
pub fn mediant(a: &RBig, b: &RBig) -> RBig {
let a_num = a.numerator().clone();
let a_den: IBig = a.denominator().clone().into();
let b_num = b.numerator().clone();
let b_den: IBig = b.denominator().clone().into();
let num = a_num + b_num;
let den = a_den + b_den;
rbig_from_signed(&num, &den)
}
pub fn mixed_number(x: &RBig) -> (IBig, RBig) {
let whole = x.trunc();
let frac = x.fract();
(whole, frac)
}
pub fn rational_floor(x: &RBig) -> IBig {
x.floor()
}
pub fn rational_ceil(x: &RBig) -> IBig {
x.ceil()
}
pub fn rational_round(x: &RBig) -> IBig {
x.round()
}
pub fn rational_truncate(x: &RBig) -> IBig {
x.trunc()
}
pub fn rational_from_integer(n: &IBig) -> RBig {
RBig::from_parts(n.clone(), UBig::ONE)
}
pub fn rational_is_integer(x: &RBig) -> bool {
*x.denominator() == UBig::ONE
}
pub fn rational_to_integer(x: &RBig) -> Option<IBig> {
if rational_is_integer(x) {
Some(x.numerator().clone())
} else {
None
}
}
pub fn rational_abs(x: &RBig) -> RBig {
if x.numerator() < &IBig::ZERO {
-x.clone()
} else {
x.clone()
}
}
pub fn rational_signum(x: &RBig) -> IBig {
if x.is_zero() {
IBig::ZERO
} else if x.numerator() < &IBig::ZERO {
IBig::from(-1)
} else {
IBig::ONE
}
}
pub fn rational_reciprocal(x: &RBig) -> OxiNumResult<RBig> {
if x.is_zero() {
return Err(OxiNumError::DivByZero);
}
let num = x.numerator().clone();
let den: IBig = x.denominator().clone().into();
let sign_negative = num < IBig::ZERO;
let abs_num = if sign_negative { -&num } else { num };
let new_num = if sign_negative { -den } else { den };
Ok(rbig_from_signed(&new_num, &abs_num))
}
pub fn rational_pow(x: &RBig, n: i32) -> OxiNumResult<RBig> {
if n == 0 {
return Ok(RBig::ONE);
}
if n > 0 {
Ok(x.pow(n as usize))
} else {
let reciprocal = rational_reciprocal(x)?;
let abs_n = n.unsigned_abs() as usize;
Ok(reciprocal.pow(abs_n))
}
}
pub(crate) fn div_floor(num: &IBig, den: &IBig) -> (IBig, IBig) {
use dashu_base::DivRem;
let (mut q, mut r) = num.clone().div_rem(den.clone());
if (r < IBig::ZERO && *den > IBig::ZERO) || (r > IBig::ZERO && *den < IBig::ZERO) {
q -= IBig::ONE;
r += den;
}
(q, r)
}
pub(crate) fn rbig_from_signed(num: &IBig, den: &IBig) -> RBig {
RBig::from_parts_signed(num.clone(), den.clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cf_355_over_113() {
let r = RBig::from_parts(IBig::from(355), UBig::from(113u32));
let cf = continued_fraction(&r);
assert_eq!(cf, vec![IBig::from(3), IBig::from(7), IBig::from(16)]);
}
#[test]
fn cf_integer() {
let r = RBig::from(5u32);
let cf = continued_fraction(&r);
assert_eq!(cf, vec![IBig::from(5)]);
}
#[test]
fn cf_half() {
let r = RBig::from_parts(IBig::from(1), UBig::from(2u32));
let cf = continued_fraction(&r);
assert_eq!(cf, vec![IBig::ZERO, IBig::from(2)]);
}
#[test]
fn cf_roundtrip() {
let r = RBig::from_parts(IBig::from(355), UBig::from(113u32));
let cf = continued_fraction(&r);
let reconstructed = from_continued_fraction(&cf).expect("ok");
assert_eq!(reconstructed, r);
}
#[test]
fn cf_roundtrip_various() {
let cases = [(22, 7u32), (1, 7u32), (100, 3u32), (17, 12u32), (1, 1u32)];
for (n, d) in cases {
let r = RBig::from_parts(IBig::from(n), UBig::from(d));
let cf = continued_fraction(&r);
let back = from_continued_fraction(&cf).expect("ok");
assert_eq!(back, r, "roundtrip failed for {n}/{d}");
}
}
#[test]
fn from_cf_pi_approx() {
let cf = vec![IBig::from(3), IBig::from(7), IBig::from(16)];
let r = from_continued_fraction(&cf).expect("ok");
assert_eq!(r, RBig::from_parts(IBig::from(355), UBig::from(113u32)));
}
#[test]
fn from_cf_empty_errors() {
assert!(from_continued_fraction(&[]).is_err());
}
#[test]
fn best_approx_pi() {
let pi = RBig::from_parts(IBig::from(355), UBig::from(113u32));
let approx = best_rational_approximation(&pi, &UBig::from(100u32));
assert_eq!(approx, RBig::from_parts(IBig::from(22), UBig::from(7u32)));
}
#[test]
fn best_approx_small_denom() {
let pi = RBig::from_parts(IBig::from(355), UBig::from(113u32));
let approx = best_rational_approximation(&pi, &UBig::from(10u32));
assert_eq!(approx, RBig::from_parts(IBig::from(22), UBig::from(7u32)));
}
#[test]
fn best_approx_denom_one() {
let r = RBig::from_parts(IBig::from(7), UBig::from(2u32));
let approx = best_rational_approximation(&r, &UBig::ONE);
assert_eq!(approx, RBig::from(3u32));
}
#[test]
fn decimal_third() {
let third = RBig::from_parts(IBig::from(1), UBig::from(3u32));
assert_eq!(to_decimal_string(&third, 6), "0.333333");
}
#[test]
fn decimal_quarter() {
let quarter = RBig::from_parts(IBig::from(1), UBig::from(4u32));
assert_eq!(to_decimal_string(&quarter, 4), "0.2500");
}
#[test]
fn decimal_negative() {
let neg = RBig::from_parts(IBig::from(-1), UBig::from(2u32));
assert_eq!(to_decimal_string(&neg, 2), "-0.50");
}
#[test]
fn decimal_improper() {
let r = RBig::from_parts(IBig::from(7), UBig::from(4u32));
assert_eq!(to_decimal_string(&r, 2), "1.75");
}
#[test]
fn decimal_zero_places() {
let r = RBig::from_parts(IBig::from(7), UBig::from(4u32));
assert_eq!(to_decimal_string(&r, 0), "1");
}
#[test]
fn mediant_basic() {
let a = RBig::from_parts(IBig::from(1), UBig::from(2u32));
let b = RBig::from_parts(IBig::from(1), UBig::from(3u32));
let m = mediant(&a, &b);
assert_eq!(m, RBig::from_parts(IBig::from(2), UBig::from(5u32)));
}
#[test]
fn mediant_between() {
let a = RBig::ZERO;
let b = RBig::ONE;
let m = mediant(&a, &b);
assert_eq!(m, RBig::from_parts(IBig::from(1), UBig::from(2u32)));
}
#[test]
fn mixed_number_basic() {
let r = RBig::from_parts(IBig::from(7), UBig::from(3u32));
let (whole, frac) = mixed_number(&r);
assert_eq!(whole, IBig::from(2));
assert_eq!(frac, RBig::from_parts(IBig::from(1), UBig::from(3u32)));
}
#[test]
fn mixed_number_negative() {
let r = RBig::from_parts(IBig::from(-7), UBig::from(3u32));
let (whole, frac) = mixed_number(&r);
assert_eq!(whole, IBig::from(-2));
assert_eq!(frac, RBig::from_parts(IBig::from(-1), UBig::from(3u32)));
}
#[test]
fn floor_ceil_round_trunc() {
let r = RBig::from_parts(IBig::from(7), UBig::from(3u32));
assert_eq!(rational_floor(&r), IBig::from(2));
assert_eq!(rational_ceil(&r), IBig::from(3));
assert_eq!(rational_round(&r), IBig::from(2));
assert_eq!(rational_truncate(&r), IBig::from(2));
}
#[test]
fn floor_ceil_negative() {
let r = RBig::from_parts(IBig::from(-7), UBig::from(3u32));
assert_eq!(rational_floor(&r), IBig::from(-3));
assert_eq!(rational_ceil(&r), IBig::from(-2));
assert_eq!(rational_truncate(&r), IBig::from(-2));
}
#[test]
fn abs_basic() {
let r = RBig::from_parts(IBig::from(-3), UBig::from(4u32));
assert_eq!(
rational_abs(&r),
RBig::from_parts(IBig::from(3), UBig::from(4u32))
);
}
#[test]
fn signum_basic() {
let pos = RBig::from_parts(IBig::from(3), UBig::from(4u32));
let neg = RBig::from_parts(IBig::from(-3), UBig::from(4u32));
assert_eq!(rational_signum(&pos), IBig::ONE);
assert_eq!(rational_signum(&neg), IBig::from(-1));
assert_eq!(rational_signum(&RBig::ZERO), IBig::ZERO);
}
#[test]
fn reciprocal_basic() {
let r = RBig::from_parts(IBig::from(3), UBig::from(4u32));
let recip = rational_reciprocal(&r).expect("ok");
assert_eq!(recip, RBig::from_parts(IBig::from(4), UBig::from(3u32)));
}
#[test]
fn reciprocal_negative() {
let r = RBig::from_parts(IBig::from(-3), UBig::from(4u32));
let recip = rational_reciprocal(&r).expect("ok");
assert_eq!(recip, RBig::from_parts(IBig::from(-4), UBig::from(3u32)));
}
#[test]
fn reciprocal_zero_errors() {
assert!(rational_reciprocal(&RBig::ZERO).is_err());
}
#[test]
fn reciprocal_roundtrip() {
let r = RBig::from_parts(IBig::from(7), UBig::from(13u32));
let recip = rational_reciprocal(&r).expect("ok");
assert_eq!(r * recip, RBig::ONE);
}
#[test]
fn pow_positive() {
let r = RBig::from_parts(IBig::from(2), UBig::from(3u32));
let result = rational_pow(&r, 2).expect("ok");
assert_eq!(result, RBig::from_parts(IBig::from(4), UBig::from(9u32)));
}
#[test]
fn pow_negative() {
let r = RBig::from_parts(IBig::from(2), UBig::from(3u32));
let result = rational_pow(&r, -1).expect("ok");
assert_eq!(result, RBig::from_parts(IBig::from(3), UBig::from(2u32)));
}
#[test]
fn pow_zero() {
let r = RBig::from_parts(IBig::from(5), UBig::from(7u32));
assert_eq!(rational_pow(&r, 0).expect("ok"), RBig::ONE);
}
#[test]
fn pow_negative_squared() {
let r = RBig::from_parts(IBig::from(2), UBig::from(3u32));
let result = rational_pow(&r, -2).expect("ok");
assert_eq!(result, RBig::from_parts(IBig::from(9), UBig::from(4u32)));
}
#[test]
fn div_floor_positive() {
let (q, r) = div_floor(&IBig::from(7), &IBig::from(3));
assert_eq!(q, IBig::from(2));
assert_eq!(r, IBig::from(1));
}
#[test]
fn div_floor_negative_dividend() {
let (q, r) = div_floor(&IBig::from(-7), &IBig::from(3));
assert_eq!(q, IBig::from(-3));
assert_eq!(r, IBig::from(2));
}
#[test]
fn from_integer_basic() {
let r = rational_from_integer(&IBig::from(42));
assert_eq!(r.numerator(), &IBig::from(42));
assert_eq!(r.denominator(), &UBig::ONE);
}
#[test]
fn from_integer_negative() {
let r = rational_from_integer(&IBig::from(-7));
assert_eq!(r.numerator(), &IBig::from(-7));
assert_eq!(r.denominator(), &UBig::ONE);
}
#[test]
fn from_integer_zero() {
let r = rational_from_integer(&IBig::ZERO);
assert_eq!(r, RBig::ZERO);
}
#[test]
fn is_integer_true_for_whole() {
let r = rbig_from_signed(&IBig::from(3), &IBig::from(1));
assert!(rational_is_integer(&r));
}
#[test]
fn is_integer_false_for_fraction() {
let r = rbig_from_signed(&IBig::from(3), &IBig::from(2));
assert!(!rational_is_integer(&r));
}
#[test]
fn is_integer_after_simplification() {
let r = RBig::from_parts(IBig::from(10), UBig::from(5u32));
assert!(rational_is_integer(&r));
}
#[test]
fn to_integer_round_trip() {
let n = IBig::from(42);
let r = rational_from_integer(&n);
assert_eq!(rational_to_integer(&r), Some(n));
}
#[test]
fn to_integer_round_trip_negative() {
let n = IBig::from(-12345);
let r = rational_from_integer(&n);
assert_eq!(rational_to_integer(&r), Some(n));
}
#[test]
fn to_integer_none_for_fraction() {
let r = RBig::from_parts(IBig::from(3), UBig::from(2u32));
assert_eq!(rational_to_integer(&r), None);
}
}