use core::cmp::Ordering;
use core::hash::{Hash, Hasher};
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
use oxinum_int::native::{gcd, BigInt};
use super::rational::BigRational;
fn add_core(a: &BigRational, b: &BigRational) -> BigRational {
let g = gcd(a.den().clone(), b.den().clone());
let b_over_g = a.den() / &g;
let d_over_g = b.den() / &g;
let lhs = &a.num * &BigInt::from(d_over_g.clone());
let rhs = &b.num * &BigInt::from(b_over_g.clone());
let num = &lhs + &rhs;
let den = &b_over_g * b.den();
BigRational::reduce_unchecked(num, den)
}
fn sub_core(a: &BigRational, b: &BigRational) -> BigRational {
let g = gcd(a.den().clone(), b.den().clone());
let b_over_g = a.den() / &g;
let d_over_g = b.den() / &g;
let lhs = &a.num * &BigInt::from(d_over_g.clone());
let rhs = &b.num * &BigInt::from(b_over_g.clone());
let num = &lhs - &rhs;
let den = &b_over_g * b.den();
BigRational::reduce_unchecked(num, den)
}
fn mul_core(a: &BigRational, b: &BigRational) -> BigRational {
let g1 = gcd(a.num.magnitude().clone(), b.den.clone()); let g2 = gcd(b.num.magnitude().clone(), a.den.clone());
let (sa, ma) = a.num.clone().into_parts();
let a_red_mag = &ma / &g1;
let a_red = BigInt::from_parts(sa, a_red_mag);
let (sc, mc) = b.num.clone().into_parts();
let c_red_mag = &mc / &g2;
let c_red = BigInt::from_parts(sc, c_red_mag);
let b_red = a.den() / &g2; let d_red = b.den() / &g1;
let num = &a_red * &c_red;
let den = &b_red * &d_red;
if num.is_zero() {
return BigRational::zero();
}
BigRational { num, den }
}
fn div_core(a: &BigRational, b: &BigRational) -> BigRational {
if b.num.is_zero() {
panic!("BigRational: division by zero");
}
let recip = b
.recip()
.expect("recip(non-zero) cannot fail — zero check above");
mul_core(a, &recip)
}
macro_rules! impl_binop {
($Trait:ident, $method:ident, $core:ident) => {
impl $Trait<&BigRational> for &BigRational {
type Output = BigRational;
#[inline]
fn $method(self, rhs: &BigRational) -> BigRational {
$core(self, rhs)
}
}
impl $Trait<BigRational> for BigRational {
type Output = BigRational;
#[inline]
fn $method(self, rhs: BigRational) -> BigRational {
$core(&self, &rhs)
}
}
impl $Trait<&BigRational> for BigRational {
type Output = BigRational;
#[inline]
fn $method(self, rhs: &BigRational) -> BigRational {
$core(&self, rhs)
}
}
impl $Trait<BigRational> for &BigRational {
type Output = BigRational;
#[inline]
fn $method(self, rhs: BigRational) -> BigRational {
$core(self, &rhs)
}
}
};
}
macro_rules! impl_assign {
($Trait:ident, $method:ident, $core:ident) => {
impl $Trait<&BigRational> for BigRational {
#[inline]
fn $method(&mut self, rhs: &BigRational) {
*self = $core(&*self, rhs);
}
}
impl $Trait<BigRational> for BigRational {
#[inline]
fn $method(&mut self, rhs: BigRational) {
*self = $core(&*self, &rhs);
}
}
};
}
impl_binop!(Add, add, add_core);
impl_binop!(Sub, sub, sub_core);
impl_binop!(Mul, mul, mul_core);
impl_binop!(Div, div, div_core);
impl_assign!(AddAssign, add_assign, add_core);
impl_assign!(SubAssign, sub_assign, sub_core);
impl_assign!(MulAssign, mul_assign, mul_core);
impl_assign!(DivAssign, div_assign, div_core);
fn rem_core(_a: &BigRational, b: &BigRational) -> BigRational {
if b.num.is_zero() {
panic!("BigRational: remainder with zero divisor");
}
BigRational::zero()
}
impl_binop!(Rem, rem, rem_core);
impl_assign!(RemAssign, rem_assign, rem_core);
impl Hash for BigRational {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.num.hash(state);
self.den.hash(state);
}
}
impl Ord for BigRational {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.cmp_impl(other)
}
}
impl PartialOrd for BigRational {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxinum_int::native::BigUint;
fn r(n: i64, d: u64) -> BigRational {
BigRational::from_parts(BigInt::from(n), BigUint::from_u64(d))
.expect("non-zero denominator")
}
#[test]
fn add_unlike_denominators() {
assert_eq!(r(1, 2) + r(1, 3), r(5, 6));
}
#[test]
fn add_cancels_to_zero() {
let sum = r(-3, 4) + r(3, 4);
assert!(sum.is_zero());
assert_eq!(sum.to_string(), "0");
}
#[test]
fn sub_unlike_denominators() {
let one = BigRational::one();
assert_eq!(one - r(1, 3), r(2, 3));
}
#[test]
fn mul_reduces_to_half() {
assert_eq!(r(2, 3) * r(3, 4), r(1, 2));
}
#[test]
fn mul_by_zero_is_zero() {
let p = r(7, 9) * BigRational::zero();
assert!(p.is_zero());
}
#[test]
fn div_basic() {
assert_eq!(r(1, 2) / r(1, 3), r(3, 2));
}
#[test]
#[should_panic(expected = "division by zero")]
fn div_by_zero_panics() {
let _ = r(1, 2) / BigRational::zero();
}
#[test]
fn rem_returns_zero() {
let rem = r(7, 3) % r(1, 2);
assert!(rem.is_zero());
}
#[test]
#[should_panic(expected = "remainder with zero divisor")]
fn rem_by_zero_panics() {
let _ = r(1, 2) % BigRational::zero();
}
#[test]
fn assign_ops_idempotent() {
let mut a = r(1, 2);
a += r(1, 3);
assert_eq!(a, r(5, 6));
a -= r(1, 3);
assert_eq!(a, r(1, 2));
a *= r(2, 1);
assert_eq!(a, BigRational::one());
a /= r(2, 1);
assert_eq!(a, r(1, 2));
}
#[test]
fn ord_basic() {
let half = r(1, 2);
let third = r(1, 3);
let neg_half = r(-1, 2);
assert!(third < half);
assert!(neg_half < third);
assert!(neg_half < half);
assert_eq!(half.cmp(&half), Ordering::Equal);
}
#[test]
fn ord_zero_handled() {
assert!(r(-1, 100) < BigRational::zero());
assert!(BigRational::zero() < r(1, 1_000_000));
}
#[test]
fn hash_matches_eq() {
use std::collections::hash_map::DefaultHasher;
let a = r(6, 4); let b = r(3, 2);
assert_eq!(a, b);
let mut ha = DefaultHasher::new();
a.hash(&mut ha);
let mut hb = DefaultHasher::new();
b.hash(&mut hb);
assert_eq!(ha.finish(), hb.finish());
}
#[test]
fn borrowed_owned_variants_consistent() {
let a = r(1, 2);
let b = r(1, 3);
let target = r(5, 6);
assert_eq!(&a + &b, target);
assert_eq!(a.clone() + b.clone(), target);
assert_eq!(a.clone() + &b, target);
assert_eq!(&a + b.clone(), target);
}
}