use crate::rational::Ratio;
use crate::rational::small::ops::building_blocks::{
gcd8, gcd16, gcd32, gcd64, gcd128, gcd_usize,
};
use crate::sign::Sign;
use crate::traits::Gcd;
use crate::{
Rational8, Rational16, Rational32, Rational64, Rational128, RationalUsize,
};
macro_rules! gcd_small {
($($name:ty => $unsigned:ty, $gcd:ident);+ $(;)?) => {$(
impl Gcd for $name {
fn gcd(&self, other: &Self) -> Option<Self> {
fn divisor(left: $unsigned, right: $unsigned) -> $unsigned {
if left == 1 || right == 1 { 1 } else { $gcd(left, right) }
}
Some(match (self.sign, other.sign) {
(Sign::Zero, Sign::Zero) => Ratio {
sign: Sign::Zero,
numerator: 0,
denominator: 1,
},
(Sign::Zero, _) => Ratio { sign: Sign::Positive, ..*other },
(_, Sign::Zero) => Ratio { sign: Sign::Positive, ..*self },
_ => {
let shared = divisor(self.denominator, other.denominator);
Ratio {
sign: Sign::Positive,
numerator: divisor(self.numerator, other.numerator),
denominator: (self.denominator / shared)
.checked_mul(other.denominator)?,
}
}
})
}
}
)+}
}
gcd_small!(
Rational8 => u8, gcd8;
Rational16 => u16, gcd16;
Rational32 => u32, gcd32;
Rational64 => u64, gcd64;
Rational128 => u128, gcd128;
RationalUsize => usize, gcd_usize;
);
#[cfg(test)]
mod test {
use crate::{Gcd, Rational8, R8, R64};
#[test]
fn the_same_answers_as_the_wide_type() {
assert_eq!(R8!(4).gcd(&R8!(6)), Some(R8!(2)));
assert_eq!(R8!(1, 2).gcd(&R8!(1, 3)), Some(R8!(1, 6)));
assert_eq!(R8!(3, 4).gcd(&R8!(5, 6)), Some(R8!(1, 12)));
assert_eq!(R8!(-4).gcd(&R8!(6)), Some(R8!(2)));
assert_eq!(R8!(0).gcd(&R8!(0)), Some(R8!(0)));
assert_eq!(R8!(0).gcd(&R8!(-6)), Some(R8!(6)));
assert_eq!(R64!(9, 10).gcd(&R64!(6, 25)), Some(R64!(3, 50)));
}
#[test]
fn a_result_that_does_not_fit_is_reported() {
assert_eq!(R8!(1, 200).gcd(&R8!(1, 3)), None);
assert_eq!(R64!(1, 200).gcd(&R64!(1, 3)), Some(R64!(1, 600)));
}
#[test]
fn dividing_by_it_leaves_coprime_integers() {
for (left, right) in [
(R8!(1, 2), R8!(1, 3)),
(R8!(3, 4), R8!(5, 6)),
(R8!(9), R8!(6)),
] {
let divisor: Rational8 = left.gcd(&right).unwrap();
let one = left / divisor;
let other = right / divisor;
assert_eq!(one * divisor, left);
assert_eq!(other * divisor, right);
assert_eq!(one.gcd(&other), Some(R8!(1)));
}
}
}