use crate::{
euclid,
traits::{CheckedNeg, DivEuclid, Zero},
};
use std::{
cmp::Ordering,
ops::{DivAssign, Mul, RemAssign},
};
#[derive(PartialEq, Eq, Clone)]
pub struct Ratio<T> {
numer: T,
denom: T,
}
impl<T> Ratio<T>
where
T: Ord
+ Clone
+ Zero
+ for<'a> RemAssign<&'a T>
+ for<'b> DivAssign<&'b T>
+ CheckedNeg<Output = T>,
{
pub fn checked_new(numer: T, denom: T) -> Option<Self> {
if denom.is_zero() {
return None;
}
let ratio = Ratio { numer, denom };
ratio.checked_reduced()
}
}
impl<T> Ratio<T>
where
T: Ord
+ Clone
+ Zero
+ for<'a> RemAssign<&'a T>
+ for<'b> DivAssign<&'b T>
+ CheckedNeg<Output = T>
+ DivEuclid<Output = T>,
{
pub fn new(numer: T, denom: T) -> Self {
assert!(!denom.is_zero());
let ratio = Ratio { numer, denom };
ratio.reduced()
}
pub fn iter_cont_frac(&self) -> IterContFrac<T> {
IterContFrac {
a: self.numer().clone(),
b: self.denom().clone(),
}
}
}
impl<T> Ratio<T> {
pub fn numer(&self) -> &T {
&self.numer
}
pub fn denom(&self) -> &T {
&self.denom
}
}
pub struct IterContFrac<T> {
a: T,
b: T,
}
impl<T> Iterator for IterContFrac<T>
where
T: Zero + Clone + DivEuclid<Output = T> + for<'a> RemAssign<&'a T>,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let ref mut a = self.a;
let ref mut b = self.b;
if b.is_zero() {
return None;
}
let coeff = a.div_euclid(b);
*a %= b;
std::mem::swap(a, b);
Some(coeff)
}
}
impl<T> PartialOrd for Ratio<T>
where
T: PartialOrd
+ Ord
+ Clone
+ Zero
+ for<'a> RemAssign<&'a T>
+ for<'b> DivAssign<&'b T>
+ CheckedNeg<Output = T>
+ DivEuclid<Output = T>,
for<'a> &'a T: Mul<Output = T>,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let mut k = 0;
let mut cont_frac_s = self.iter_cont_frac();
let mut cont_frac_o = other.iter_cont_frac();
loop {
match (cont_frac_s.next(), cont_frac_o.next(), k % 2) {
(None, None, _) => return Some(Ordering::Equal),
(None, Some(_), 0) => return Some(Ordering::Greater),
(None, Some(_), _) => return Some(Ordering::Less),
(Some(_), None, 0) => return Some(Ordering::Less),
(Some(_), None, _) => return Some(Ordering::Greater),
(Some(ref c_s), Some(ref c_o), 0) => match c_s.partial_cmp(c_o)? {
Ordering::Greater => return Some(Ordering::Greater),
Ordering::Less => return Some(Ordering::Less),
Ordering::Equal => k += 1,
},
(Some(ref c_s), Some(ref c_o), _) => match c_s.partial_cmp(c_o)? {
Ordering::Greater => return Some(Ordering::Less),
Ordering::Less => return Some(Ordering::Greater),
Ordering::Equal => k += 1,
},
}
}
}
}
impl<T> Ord for Ratio<T>
where
T: Ord,
for<'a> &'a T: Mul<Output = T>,
Ratio<T>: PartialOrd,
{
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl<T> Ratio<T>
where
T: Ord
+ Clone
+ Zero
+ for<'a> RemAssign<&'a T>
+ for<'b> DivAssign<&'b T>
+ CheckedNeg<Output = T>,
{
fn checked_reduced(mut self) -> Option<Self> {
let numer = self.numer.clone();
let denom = self.denom.clone();
let gcd = euclid::checked_gcd(numer, denom)?;
self.numer /= &gcd;
self.denom /= &gcd;
if self.denom < Zero::zero() {
self.numer = self.numer.checked_neg()?;
self.denom = self.denom.checked_neg()?;
}
Some(self)
}
}
impl<T> Ratio<T>
where
T: Ord
+ Clone
+ Zero
+ for<'a> RemAssign<&'a T>
+ for<'b> DivAssign<&'b T>
+ CheckedNeg<Output = T>,
{
fn reduced(mut self) -> Self {
let numer = self.numer.clone();
let denom = self.denom.clone();
let gcd = euclid::gcd(numer, denom);
self.numer /= &gcd;
self.denom /= &gcd;
if self.denom < Zero::zero() {
self.numer = self.numer.checked_neg().unwrap();
self.denom = self.denom.checked_neg().unwrap();
}
self
}
}