use ebi_objects::{
anyhow::{Result, anyhow},
ebi_arithmetic::{
ebi_number::Zero,
exact::is_exact_globally,
fraction::fraction_enum::FractionEnum,
malachite::{Natural, base::num::arithmetic::traits::Lcm, rational::Rational},
},
};
use std::{
borrow::Borrow,
ops::{AddAssign, Mul},
sync::Arc,
};
#[derive(Clone)]
pub enum FixedDenominatorFractionEnum {
Zero, Exact(Natural, Arc<Natural>),
Approximate(f64),
CannotCombineExactAndApprox,
}
impl FixedDenominatorFractionEnum {
pub fn create(fractions: &Vec<Arc<FractionEnum>>) -> Result<Vec<Arc<Self>>> {
match fractions.iter().next() {
None => Ok(vec![]),
Some(x) => match x.as_ref() {
FractionEnum::CannotCombineExactAndApprox => {
Err(anyhow!("cannot combine exact and approximate arithmetic"))
}
FractionEnum::Approx(_) => {
Ok(fractions
.into_iter()
.map(|f| match f.as_ref() {
FractionEnum::Approx(f) => Arc::new(Self::Approximate(f.clone())),
_ => Arc::new(Self::CannotCombineExactAndApprox),
})
.collect::<Vec<_>>())
}
FractionEnum::Exact(_) => {
let denominators = fractions
.iter()
.filter_map(|f| match f.as_ref() {
FractionEnum::Exact(r) => Some(r.to_denominator()),
_ => None,
})
.collect::<Vec<_>>();
let lowest_common_multiple =
Arc::new(Self::lowest_common_multiple(&denominators)?);
Ok(fractions
.iter()
.map(|f| match f.as_ref() {
FractionEnum::Exact(r) => {
let mut x = r.to_numerator() * lowest_common_multiple.as_ref();
x /= r.to_denominator();
Arc::new(Self::Exact(x, lowest_common_multiple.clone()))
}
_ => Arc::new(Self::CannotCombineExactAndApprox),
})
.collect::<Vec<_>>())
}
},
}
}
pub fn lowest_common_multiple(numbers: &[Natural]) -> Result<Natural> {
if numbers.is_empty() {
return Err(anyhow!("cannot compute lcm on empty list"));
}
let mut it = numbers.iter();
let mut result = it.next().unwrap().to_owned().to_owned();
while let Some(number) = it.next() {
result = result.lcm(number);
}
Ok(result)
}
pub(crate) fn matches(&self, rhs: &Self) -> bool {
match (self, rhs) {
(Self::Zero, Self::Exact(_, _)) => true,
(Self::Exact(_, _), Self::Zero) => true,
(Self::Zero, Self::Zero) => true,
(Self::Exact(_, denom1), Self::Exact(_, denom2)) => denom1 == denom2,
(Self::Approximate(_), Self::Approximate(_)) => true,
_ => false,
}
}
pub fn to_fraction(self) -> FractionEnum {
match self {
Self::Exact(numer, denom) => {
FractionEnum::Exact(Rational::from(numer) / Rational::from(denom.as_ref()))
}
Self::Approximate(f) => FractionEnum::Approx(f),
Self::Zero => FractionEnum::zero(),
_ => FractionEnum::CannotCombineExactAndApprox,
}
}
}
impl Zero for FixedDenominatorFractionEnum {
fn zero() -> Self {
if is_exact_globally() {
Self::Zero
} else {
Self::Approximate(0.0)
}
}
fn is_zero(&self) -> bool {
match self {
FixedDenominatorFractionEnum::Zero => true,
FixedDenominatorFractionEnum::Exact(x, _) => x.is_zero(),
FixedDenominatorFractionEnum::Approximate(f) => f.is_zero(),
FixedDenominatorFractionEnum::CannotCombineExactAndApprox => false,
}
}
}
impl<T> AddAssign<T> for FixedDenominatorFractionEnum
where
T: Borrow<FixedDenominatorFractionEnum>,
{
fn add_assign(&mut self, rhs: T) {
if let Self::Zero = self {
*self = rhs.borrow().clone()
} else if let Self::Zero = rhs.borrow() {
} else if self.matches(rhs.borrow()) {
match (self, rhs.borrow()) {
(Self::Exact(x, _), Self::Exact(y, _)) => x.add_assign(y),
(Self::Approximate(x), Self::Approximate(y)) => x.add_assign(y),
_ => {}
};
} else {
*self = Self::CannotCombineExactAndApprox
}
}
}
impl AddAssign<&Arc<FixedDenominatorFractionEnum>> for FixedDenominatorFractionEnum {
fn add_assign(&mut self, rhs: &Arc<FixedDenominatorFractionEnum>) {
if let Self::Zero = self {
*self = rhs.as_ref().clone()
} else if let Self::Zero = rhs.borrow() {
} else if self.matches(rhs.borrow()) {
match (self, rhs.borrow()) {
(Self::Exact(x, _), Self::Exact(y, _)) => x.add_assign(y),
(Self::Approximate(x), Self::Approximate(y)) => x.add_assign(y),
_ => {}
};
} else {
*self = Self::CannotCombineExactAndApprox
}
}
}
impl Mul<u64> for FixedDenominatorFractionEnum {
type Output = FixedDenominatorFractionEnum;
fn mul(self, rhs: u64) -> Self::Output {
match self {
Self::Zero => Self::Zero,
Self::Exact(mut x, denom) => {
x *= Natural::from(rhs);
Self::Exact(x, denom)
}
Self::Approximate(mut x) => {
x *= rhs as f64;
Self::Approximate(x)
}
Self::CannotCombineExactAndApprox => Self::CannotCombineExactAndApprox,
}
}
}