ebi/
fraction_enum.rs

1use anyhow::{Error, Result, anyhow};
2use fraction::{BigFraction, BigUint, GenericFraction, Sign};
3use num::{BigInt, One as NumOne};
4use num_bigint::{ToBigInt, ToBigUint};
5use num_rational::Ratio;
6use num_traits::ToPrimitive;
7use std::{
8    borrow::Borrow,
9    cmp::Ordering,
10    f64,
11    hash::Hash,
12    iter::Sum,
13    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
14    str::FromStr,
15    sync::Arc,
16};
17
18use crate::{
19    exact::{is_exact_globally, MaybeExact},
20    fraction::{UInt, EPSILON},
21    traits::Infinite,
22};
23
24use super::traits::{One, Signed, Zero};
25
26#[derive(Clone)]
27pub enum FractionEnum {
28    Exact(fraction::BigFraction),
29    Approx(f64),
30    CannotCombineExactAndApprox,
31}
32
33impl FractionEnum {
34    /**
35     * Returns whether the two given fractions are either both exact or both approximate
36     */
37    pub(crate) fn matches(&self, rhs: &Self) -> bool {
38        match (self, rhs) {
39            (FractionEnum::Exact(_), FractionEnum::Exact(_)) => true,
40            (FractionEnum::Approx(_), FractionEnum::Approx(_)) => true,
41            _ => false,
42        }
43    }
44
45    pub fn sqrt_abs(&self, decimal_places: u32) -> FractionEnum {
46        match self {
47            FractionEnum::Exact(f) => FractionEnum::Exact(f.sqrt_abs(decimal_places)),
48            FractionEnum::Approx(f) => FractionEnum::Approx(f.abs().sqrt()),
49            FractionEnum::CannotCombineExactAndApprox => self.clone(),
50        }
51    }
52
53    pub fn is_sign_negative(&self) -> bool {
54        match self {
55            FractionEnum::Exact(f) => f.is_sign_negative(),
56            FractionEnum::Approx(f) => f.is_sign_negative(),
57            FractionEnum::CannotCombineExactAndApprox => true,
58        }
59    }
60
61    pub fn is_sign_positive(&self) -> bool {
62        match self {
63            FractionEnum::Exact(f) => f.is_sign_positive(),
64            FractionEnum::Approx(f) => f.is_sign_positive(),
65            FractionEnum::CannotCombineExactAndApprox => false,
66        }
67    }
68
69    /// Returns true if the value is Infinity (does not matter positive or negative)
70    pub fn is_infinite(&self) -> bool {
71        match self {
72            FractionEnum::Exact(f) => f.is_infinite(),
73            FractionEnum::Approx(f) => f.is_infinite(),
74            FractionEnum::CannotCombineExactAndApprox => false,
75        }
76    }
77
78    pub fn is_nan(&self) -> bool {
79        match self {
80            FractionEnum::Exact(f) => f.is_nan(),
81            FractionEnum::Approx(f) => f.is_nan(),
82            FractionEnum::CannotCombineExactAndApprox => true,
83        }
84    }
85
86    pub fn infinity() -> Self {
87        if is_exact_globally() {
88            FractionEnum::Exact(BigFraction::infinity())
89        } else {
90            FractionEnum::Approx(f64::INFINITY)
91        }
92    }
93
94    pub fn neg_infinity() -> Self {
95        if is_exact_globally() {
96            FractionEnum::Exact(BigFraction::neg_infinity())
97        } else {
98            FractionEnum::Approx(f64::NEG_INFINITY)
99        }
100    }
101
102    pub fn nan() -> Self {
103        if is_exact_globally() {
104            FractionEnum::Exact(BigFraction::nan())
105        } else {
106            FractionEnum::Approx(f64::NAN)
107        }
108    }
109
110    pub fn sign(&self) -> Option<Sign> {
111        match self {
112            FractionEnum::Exact(f) => f.sign(),
113            FractionEnum::Approx(f) => {
114                if f.is_nan() {
115                    None
116                } else if <f64 as Zero>::is_zero(f) {
117                    Some(Sign::Plus)
118                } else if f.is_sign_positive() {
119                    Some(Sign::Plus)
120                } else {
121                    Some(Sign::Minus)
122                }
123            }
124            FractionEnum::CannotCombineExactAndApprox => None,
125        }
126    }
127
128    /**
129     * 1/self
130     */
131    pub fn recip(&self) -> Self {
132        match self {
133            FractionEnum::Exact(f) => FractionEnum::Exact(f.recip()),
134            FractionEnum::Approx(f) => FractionEnum::Approx(f.recip()),
135            FractionEnum::CannotCombineExactAndApprox => self.clone(),
136        }
137    }
138
139    pub fn one_minus(self) -> Self {
140        match self {
141            FractionEnum::Exact(mut f) => {
142                f = f.neg();
143                f.add_assign(1.to_biguint().unwrap());
144                FractionEnum::Exact(f)
145            }
146            FractionEnum::Approx(f) => FractionEnum::Approx(1.0 - f),
147            Self::CannotCombineExactAndApprox => self,
148        }
149    }
150
151    pub fn two() -> FractionEnum {
152        if is_exact_globally() {
153            FractionEnum::Exact(GenericFraction::Rational(
154                Sign::Plus,
155                Ratio::new_raw(UInt::from(2u32), UInt::from(1u32)),
156            ))
157        } else {
158            FractionEnum::Approx(2.0)
159        }
160    }
161}
162
163impl MaybeExact for FractionEnum {
164    type Approximate = f64;
165    type Exact = BigFraction;
166
167    fn is_exact(&self) -> bool {
168        match self {
169            FractionEnum::Exact(_) => true,
170            FractionEnum::Approx(_) => false,
171            FractionEnum::CannotCombineExactAndApprox => false,
172        }
173    }
174
175    fn extract_approx(&self) -> Result<f64> {
176        match self {
177            FractionEnum::Exact(_) => Err(anyhow!("cannot extract a float from a fraction")),
178            FractionEnum::Approx(f) => Ok(*f),
179            FractionEnum::CannotCombineExactAndApprox => {
180                Err(anyhow!("cannot combine exact and approximate arithmetic"))
181            }
182        }
183    }
184
185    fn extract_exact(&self) -> Result<&GenericFraction<BigUint>> {
186        match self {
187            FractionEnum::Exact(generic_fraction) => Ok(generic_fraction),
188            FractionEnum::Approx(_) => Err(anyhow!("cannot extract a fraction from a float")),
189            FractionEnum::CannotCombineExactAndApprox => {
190                Err(anyhow!("cannot combine exact and approximate arithmetic"))
191            }
192        }
193    }
194}
195
196impl One for FractionEnum {
197    fn one() -> Self {
198        if is_exact_globally() {
199            FractionEnum::Exact(GenericFraction::Rational(Sign::Plus, Ratio::one()))
200        } else {
201            FractionEnum::Approx(1.0)
202        }
203    }
204
205    fn is_one(&self) -> bool {
206        match self {
207            FractionEnum::Exact(f) => fraction::One::is_one(f),
208            FractionEnum::Approx(f) => (f - 1.0).abs() < EPSILON,
209            Self::CannotCombineExactAndApprox => false,
210        }
211    }
212
213    fn set_one(&mut self) {
214        match self {
215            FractionEnum::Exact(f) => num::One::set_one(f),
216            FractionEnum::Approx(f) => *f = 1.0,
217            FractionEnum::CannotCombineExactAndApprox => {}
218        }
219    }
220}
221
222impl Zero for FractionEnum {
223    fn zero() -> Self {
224        if is_exact_globally() {
225            FractionEnum::Exact(GenericFraction::Rational(Sign::Plus, num::Zero::zero()))
226        } else {
227            FractionEnum::Approx(<f64 as Zero>::zero())
228        }
229    }
230
231    fn is_zero(&self) -> bool {
232        match self {
233            FractionEnum::Exact(f) => fraction::Zero::is_zero(f),
234            FractionEnum::Approx(f) => f.abs() - &EPSILON < 0.0,
235            Self::CannotCombineExactAndApprox => false,
236        }
237    }
238
239    fn set_zero(&mut self) {
240        match self {
241            FractionEnum::Exact(f) => num::Zero::set_zero(f),
242            FractionEnum::Approx(f) => *f = 0.0,
243            FractionEnum::CannotCombineExactAndApprox => {}
244        }
245    }
246}
247
248impl Signed for FractionEnum {
249    fn abs(&self) -> Self {
250        match self {
251            FractionEnum::Exact(f) => FractionEnum::Exact(f.abs()),
252            FractionEnum::Approx(f) => FractionEnum::Approx(f.abs()),
253            FractionEnum::CannotCombineExactAndApprox => self.clone(),
254        }
255    }
256
257    fn is_positive(&self) -> bool {
258        match self {
259            FractionEnum::Exact(f) => !num::Zero::is_zero(f) && fraction::Signed::is_positive(f),
260            FractionEnum::Approx(f) => f.is_positive(),
261            FractionEnum::CannotCombineExactAndApprox => false,
262        }
263    }
264
265    fn is_negative(&self) -> bool {
266        match self {
267            FractionEnum::Exact(f) => fraction::Signed::is_negative(f),
268            FractionEnum::Approx(f) => f.is_negative(),
269            FractionEnum::CannotCombineExactAndApprox => false,
270        }
271    }
272
273    fn is_not_negative(&self) -> bool {
274        match self {
275            FractionEnum::Exact(_) => !self.is_negative(),
276            FractionEnum::Approx(f) => f.is_not_negative(),
277            FractionEnum::CannotCombineExactAndApprox => false,
278        }
279    }
280
281    fn is_not_positive(&self) -> bool {
282        match self {
283            FractionEnum::Exact(_) => !self.is_positive(),
284            FractionEnum::Approx(f) => f.is_not_positive(),
285            FractionEnum::CannotCombineExactAndApprox => false,
286        }
287    }
288}
289
290impl Infinite for FractionEnum {
291    fn is_infinite(&self) -> bool {
292        match self {
293            FractionEnum::Exact(f) => f.is_infinite(),
294            FractionEnum::Approx(f) => f.is_infinite(),
295            FractionEnum::CannotCombineExactAndApprox => false,
296        }
297    }
298}
299
300impl FromStr for FractionEnum {
301    type Err = Error;
302
303    fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
304        if is_exact_globally() {
305            Ok(FractionEnum::Exact(BigFraction::from_str(s)?))
306        } else {
307            if let Ok(float) = f64::from_str(s) {
308                Ok(FractionEnum::Approx(float))
309            } else {
310                let fraction = BigFraction::from_str(s)?;
311                match fraction.to_f64() {
312                    Some(f) => Ok(FractionEnum::Approx(f)),
313                    None => Err(anyhow!("could not read fraction {} as float", s)),
314                }
315            }
316        }
317    }
318}
319
320
321
322
323impl From<&FractionEnum> for FractionEnum {
324    fn from(value: &FractionEnum) -> Self {
325        match value {
326            FractionEnum::Exact(_) => value.clone(),
327            FractionEnum::Approx(_) => value.clone(),
328            FractionEnum::CannotCombineExactAndApprox => value.clone(),
329        }
330    }
331}
332
333impl From<Arc<FractionEnum>> for FractionEnum {
334    fn from(value: Arc<FractionEnum>) -> Self {
335        match value.as_ref() {
336            FractionEnum::Exact(f) => FractionEnum::Exact(f.clone()),
337            FractionEnum::Approx(f) => FractionEnum::Approx(f.clone()),
338            FractionEnum::CannotCombineExactAndApprox => FractionEnum::CannotCombineExactAndApprox,
339        }
340    }
341}
342
343impl From<&Arc<FractionEnum>> for FractionEnum {
344    fn from(value: &Arc<FractionEnum>) -> Self {
345        match value.as_ref() {
346            FractionEnum::Exact(f) => FractionEnum::Exact(f.clone()),
347            FractionEnum::Approx(f) => FractionEnum::Approx(f.clone()),
348            FractionEnum::CannotCombineExactAndApprox => FractionEnum::CannotCombineExactAndApprox,
349        }
350    }
351}
352
353impl TryFrom<BigUint> for FractionEnum {
354    type Error = Error;
355
356    fn try_from(value: BigUint) -> std::prelude::v1::Result<Self, Self::Error> {
357        if is_exact_globally() {
358            Ok(FractionEnum::Exact(GenericFraction::Rational(
359                Sign::Plus,
360                Ratio::new(value, UInt::from(1u32)),
361            )))
362        } else {
363            if value < u64::MAX.to_biguint().unwrap() {
364                Ok(FractionEnum::Approx(value.to_f64().unwrap()))
365            } else {
366                Err(anyhow!("value too large for approximate arithmetic"))
367            }
368        }
369    }
370}
371
372impl TryFrom<&BigUint> for FractionEnum {
373    type Error = Error;
374
375    fn try_from(value: &BigUint) -> std::prelude::v1::Result<Self, Self::Error> {
376        if is_exact_globally() {
377            Ok(FractionEnum::Exact(GenericFraction::Rational(
378                Sign::Plus,
379                Ratio::new(value.clone(), UInt::from(1u32)),
380            )))
381        } else {
382            if value < &u64::MAX.to_biguint().unwrap() {
383                Ok(FractionEnum::Approx(value.to_f64().unwrap()))
384            } else {
385                Err(anyhow!("value too large for approximate arithmetic"))
386            }
387        }
388    }
389}
390
391impl TryFrom<BigInt> for FractionEnum {
392    type Error = Error;
393
394    fn try_from(value: BigInt) -> std::prelude::v1::Result<Self, Self::Error> {
395        if is_exact_globally() {
396            Ok(FractionEnum::Exact(GenericFraction::Rational(
397                if value.is_negative() {
398                    Sign::Minus
399                } else {
400                    Sign::Plus
401                },
402                Ratio::new(value.abs().to_biguint().unwrap(), UInt::from(1u32)),
403            )))
404        } else {
405            if value < u64::MAX.to_bigint().unwrap() {
406                Ok(FractionEnum::Approx(value.to_f64().unwrap()))
407            } else {
408                Err(anyhow!("value too large for approximate arithmetic"))
409            }
410        }
411    }
412}
413
414impl TryFrom<(BigUint, BigUint)> for FractionEnum {
415    type Error = Error;
416
417    fn try_from(value: (BigUint, BigUint)) -> std::prelude::v1::Result<Self, Self::Error> {
418        if is_exact_globally() {
419            Ok(FractionEnum::Exact(GenericFraction::Rational(
420                Sign::Plus,
421                Ratio::new(value.0, value.1),
422            )))
423        } else {
424            if let (Some(numer), Some(denom)) = (value.0.to_u64(), value.1.to_u64()) {
425                Ok(FractionEnum::Approx(numer as f64 / denom as f64))
426            } else {
427                Err(anyhow!("numbers too large for approximate arithmetic"))
428            }
429        }
430    }
431}
432
433impl std::fmt::Display for FractionEnum {
434    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435        match self {
436            FractionEnum::Exact(fr) => std::fmt::Display::fmt(&fr, f),
437            FractionEnum::Approx(fr) => std::fmt::Display::fmt(&fr, f),
438            FractionEnum::CannotCombineExactAndApprox => {
439                write!(f, "cannot combine exact and approximate arithmatic")
440            }
441        }
442    }
443}
444
445impl std::fmt::Debug for FractionEnum {
446    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
447        match self {
448            Self::Exact(arg0) => f.debug_tuple("Exact ").field(arg0).finish(),
449            Self::Approx(arg0) => f.debug_tuple("Approx ").field(arg0).finish(),
450            Self::CannotCombineExactAndApprox => {
451                write!(f, "cannot combine exact and approximate arithmatic")
452            }
453        }
454    }
455}
456
457impl Add<&FractionEnum> for &FractionEnum {
458    type Output = FractionEnum;
459
460    fn add(self, rhs: &FractionEnum) -> Self::Output {
461        match (self, rhs) {
462            (FractionEnum::Exact(x), FractionEnum::Exact(y)) => FractionEnum::Exact(x.add(y)),
463            (FractionEnum::Approx(x), FractionEnum::Approx(y)) => FractionEnum::Approx(x.add(y)),
464            _ => FractionEnum::CannotCombineExactAndApprox,
465        }
466    }
467}
468
469impl Add<FractionEnum> for FractionEnum {
470    type Output = FractionEnum;
471
472    fn add(self, rhs: FractionEnum) -> Self::Output {
473        match (self, rhs) {
474            (FractionEnum::Exact(x), FractionEnum::Exact(y)) => FractionEnum::Exact(x.add(y)),
475            (FractionEnum::Approx(x), FractionEnum::Approx(y)) => FractionEnum::Approx(x.add(y)),
476            _ => FractionEnum::CannotCombineExactAndApprox,
477        }
478    }
479}
480
481impl<T> AddAssign<T> for FractionEnum
482where
483    T: Borrow<FractionEnum>,
484{
485    fn add_assign(&mut self, rhs: T) {
486        let rhs = rhs.borrow();
487
488        if self.matches(&rhs) {
489            match (self, rhs) {
490                (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.add_assign(y),
491                (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.add_assign(y),
492                _ => {}
493            };
494        } else {
495            *self = FractionEnum::CannotCombineExactAndApprox
496        }
497    }
498}
499
500impl AddAssign<&Arc<FractionEnum>> for FractionEnum {
501    fn add_assign(&mut self, rhs: &Arc<FractionEnum>) {
502        let rhs = rhs.borrow();
503
504        if self.matches(&rhs) {
505            match (self, rhs) {
506                (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.add_assign(y),
507                (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.add_assign(y),
508                _ => {}
509            };
510        } else {
511            *self = FractionEnum::CannotCombineExactAndApprox
512        }
513    }
514}
515
516impl Sub<&FractionEnum> for &FractionEnum {
517    type Output = FractionEnum;
518
519    fn sub(self, rhs: &FractionEnum) -> Self::Output {
520        match (self, rhs) {
521            (FractionEnum::Exact(x), FractionEnum::Exact(y)) => FractionEnum::Exact(x.sub(y)),
522            (FractionEnum::Approx(x), FractionEnum::Approx(y)) => FractionEnum::Approx(x.sub(y)),
523            _ => FractionEnum::CannotCombineExactAndApprox,
524        }
525    }
526}
527
528impl<T> SubAssign<T> for FractionEnum
529where
530    T: Borrow<FractionEnum>,
531{
532    fn sub_assign(&mut self, rhs: T) {
533        let rhs = rhs.borrow();
534        if self.matches(&rhs) {
535            match (self, rhs) {
536                (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.sub_assign(y),
537                (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.sub_assign(y),
538                _ => {}
539            }
540        } else {
541            *self = FractionEnum::CannotCombineExactAndApprox;
542        }
543    }
544}
545
546impl Mul<&FractionEnum> for &FractionEnum {
547    type Output = FractionEnum;
548
549    fn mul(self, rhs: &FractionEnum) -> Self::Output {
550        match (self, rhs) {
551            (FractionEnum::Exact(x), FractionEnum::Exact(y)) => FractionEnum::Exact(x.mul(y)),
552            (FractionEnum::Approx(x), FractionEnum::Approx(y)) => FractionEnum::Approx(x.mul(y)),
553            _ => FractionEnum::CannotCombineExactAndApprox,
554        }
555    }
556}
557
558impl<T> MulAssign<T> for FractionEnum
559where
560    T: Borrow<FractionEnum>,
561{
562    fn mul_assign(&mut self, rhs: T) {
563        let rhs = rhs.borrow();
564        if self.matches(&rhs) {
565            match (self, rhs) {
566                (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.mul_assign(y),
567                (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.mul_assign(y),
568                _ => {}
569            }
570        } else {
571            *self = FractionEnum::CannotCombineExactAndApprox
572        }
573    }
574}
575
576impl Div<&FractionEnum> for &FractionEnum {
577    type Output = FractionEnum;
578
579    fn div(self, rhs: &FractionEnum) -> Self::Output {
580        match (self, rhs) {
581            (FractionEnum::Exact(x), FractionEnum::Exact(y)) => FractionEnum::Exact(x.div(y)),
582            (FractionEnum::Approx(x), FractionEnum::Approx(y)) => FractionEnum::Approx(x.div(y)),
583            _ => FractionEnum::CannotCombineExactAndApprox,
584        }
585    }
586}
587
588impl<T> DivAssign<T> for FractionEnum
589where
590    T: Borrow<FractionEnum>,
591{
592    fn div_assign(&mut self, rhs: T) {
593        let rhs = rhs.borrow();
594        if self.matches(&rhs) {
595            match (self, rhs) {
596                (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.div_assign(y),
597                (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.div_assign(y),
598                _ => {}
599            }
600        } else {
601            *self = FractionEnum::CannotCombineExactAndApprox
602        }
603    }
604}
605
606impl Neg for FractionEnum {
607    type Output = FractionEnum;
608
609    fn neg(self) -> Self::Output {
610        match self {
611            FractionEnum::Exact(f) => FractionEnum::Exact(f.neg()),
612            FractionEnum::Approx(f) => FractionEnum::Approx(f.neg()),
613            Self::CannotCombineExactAndApprox => self.clone(),
614        }
615    }
616}
617
618impl<'a> Neg for &'a FractionEnum {
619    type Output = FractionEnum;
620
621    fn neg(self) -> Self::Output {
622        match self {
623            FractionEnum::Exact(f) => FractionEnum::Exact(f.neg()),
624            FractionEnum::Approx(f) => FractionEnum::Approx(f.neg()),
625            FractionEnum::CannotCombineExactAndApprox => self.clone(),
626        }
627    }
628}
629
630impl PartialEq for FractionEnum {
631    fn eq(&self, other: &Self) -> bool {
632        match (self, other) {
633            (Self::Exact(l0), Self::Exact(r0)) => l0 == r0,
634            (Self::Approx(l0), Self::Approx(r0)) => l0 - EPSILON <= *r0 && *r0 <= l0 + EPSILON,
635            _ => false,
636        }
637    }
638}
639
640impl Eq for FractionEnum {}
641
642impl PartialOrd for FractionEnum {
643    /**
644     * Note that exact and approximate should not be compared.
645     */
646    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
647        if !self.matches(other) {
648            panic!("cannot compare exact and inexact arithmethic");
649        }
650        match (self, other) {
651            (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.partial_cmp(y),
652            (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.partial_cmp(y),
653            _ => None,
654        }
655    }
656}
657
658impl Ord for FractionEnum {
659    /**
660     * Note that exact and approximate should not be compared.
661     */
662    fn cmp(&self, other: &Self) -> Ordering {
663        if !self.matches(other) {
664            panic!("cannot compare exact and inexact arithmethic");
665        }
666        match (self, other) {
667            (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.cmp(y),
668            (FractionEnum::Approx(x), FractionEnum::Approx(y)) => {
669                if x.is_nan() && y.is_nan() {
670                    Ordering::Equal
671                } else if x.is_nan() {
672                    Ordering::Less
673                } else if y.is_nan() {
674                    Ordering::Greater
675                } else if x == &f64::INFINITY {
676                    if y == &f64::INFINITY {
677                        Ordering::Equal
678                    } else {
679                        Ordering::Greater
680                    }
681                } else if y == &f64::INFINITY {
682                    Ordering::Less
683                } else if x == &f64::NEG_INFINITY {
684                    if y == &f64::NEG_INFINITY {
685                        Ordering::Equal
686                    } else {
687                        Ordering::Less
688                    }
689                } else if y == &f64::NEG_INFINITY {
690                    Ordering::Greater
691                } else {
692                    x.partial_cmp(y).unwrap()
693                }
694            }
695            (FractionEnum::Exact(_), FractionEnum::Approx(_)) => Ordering::Greater,
696            (FractionEnum::Exact(_), FractionEnum::CannotCombineExactAndApprox) => {
697                Ordering::Greater
698            }
699            (FractionEnum::Approx(_), FractionEnum::Exact(_)) => Ordering::Less,
700            (FractionEnum::Approx(_), FractionEnum::CannotCombineExactAndApprox) => {
701                Ordering::Greater
702            }
703            (FractionEnum::CannotCombineExactAndApprox, FractionEnum::Exact(_)) => Ordering::Less,
704            (FractionEnum::CannotCombineExactAndApprox, FractionEnum::Approx(_)) => Ordering::Less,
705            (
706                FractionEnum::CannotCombineExactAndApprox,
707                FractionEnum::CannotCombineExactAndApprox,
708            ) => Ordering::Less,
709        }
710    }
711}
712
713impl Hash for FractionEnum {
714    /**
715     * For good reasons, Rust does not support hashing of doubles. However, we need it to store distributions in a hashmap.
716     * Approximate arithmetic is discouraged
717     */
718    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
719        match self {
720            FractionEnum::Exact(f) => f.hash(state),
721            FractionEnum::Approx(f) => f64::to_bits(*f).hash(state),
722            Self::CannotCombineExactAndApprox => "cceaa".hash(state),
723        }
724    }
725}
726
727impl Sum for FractionEnum {
728    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
729        iter.fold(Self::zero(), |sum, f| &sum + &f)
730    }
731}
732
733impl<'a> Sum<&'a FractionEnum> for FractionEnum {
734    fn sum<I: Iterator<Item = &'a FractionEnum>>(iter: I) -> Self {
735        iter.fold(FractionEnum::zero(), |sum, f| &sum + f)
736    }
737}
738
739//======================== primitive types ========================//
740
741macro_rules! from {
742    ($t:ident) => {
743        impl From<$t> for FractionEnum {
744            fn from(value: $t) -> Self {
745                if is_exact_globally() {
746                    FractionEnum::Exact(GenericFraction::Rational(
747                        Sign::Plus,
748                        Ratio::new(value.to_biguint().unwrap(), UInt::from(1u32)),
749                    ))
750                } else {
751                    FractionEnum::Approx(value as f64)
752                }
753            }
754        }
755    };
756}
757
758macro_rules! from_signed {
759    ($t:ident) => {
760        impl From<$t> for FractionEnum {
761            fn from(value: $t) -> Self {
762                if is_exact_globally() {
763                    FractionEnum::Exact(GenericFraction::Rational(
764                        if value.is_negative() {
765                            Sign::Minus
766                        } else {
767                            Sign::Plus
768                        },
769                        Ratio::new(value.abs().to_biguint().unwrap(), UInt::from(1u32)),
770                    ))
771                } else {
772                    FractionEnum::Approx(value as f64)
773                }
774            }
775        }
776    };
777}
778
779macro_rules! from_tuple_u_u {
780    ($t:ident,$tt:ident) => {
781        impl From<($t, $tt)> for FractionEnum {
782            fn from(value: ($t, $tt)) -> Self {
783                if is_exact_globally() {
784                    FractionEnum::Exact(GenericFraction::Rational(
785                        Sign::Plus,
786                        Ratio::new(UInt::from(value.0), UInt::from(value.1)),
787                    ))
788                } else {
789                    FractionEnum::Approx(value.0 as f64 / value.1 as f64)
790                }
791            }
792        }
793    };
794}
795
796macro_rules! from_tuple_u_i {
797    ($t:ident,$tt:ident) => {
798        impl From<($t, $tt)> for FractionEnum {
799            fn from(value: ($t, $tt)) -> Self {
800                if is_exact_globally() {
801                    let s1 = if value.1.is_negative() {
802                        Sign::Minus
803                    } else {
804                        Sign::Plus
805                    };
806                    FractionEnum::Exact(GenericFraction::Rational(
807                        s1,
808                        Ratio::new(UInt::from(value.0), UInt::from(value.1.abs() as u128)),
809                    ))
810                } else {
811                    FractionEnum::Approx(value.0 as f64 / value.1 as f64)
812                }
813            }
814        }
815    };
816}
817
818macro_rules! from_tuple_i_u {
819    ($t:ident,$tt:ident) => {
820        impl From<($t, $tt)> for FractionEnum {
821            fn from(value: ($t, $tt)) -> Self {
822                if is_exact_globally() {
823                    let s1 = if value.0.is_negative() {
824                        Sign::Minus
825                    } else {
826                        Sign::Plus
827                    };
828                    FractionEnum::Exact(GenericFraction::Rational(
829                        s1,
830                        Ratio::new(UInt::from(value.0.abs() as u128), UInt::from(value.1)),
831                    ))
832                } else {
833                    FractionEnum::Approx(value.0 as f64 / value.1 as f64)
834                }
835            }
836        }
837    };
838}
839
840macro_rules! from_tuple_i_i {
841    ($t:ident,$tt:ident) => {
842        impl From<($t, $tt)> for FractionEnum {
843            fn from(value: ($t, $tt)) -> Self {
844                if is_exact_globally() {
845                    let s0 = if value.0.is_negative() {
846                        Sign::Minus
847                    } else {
848                        Sign::Plus
849                    };
850                    let s1 = if value.1.is_negative() {
851                        Sign::Minus
852                    } else {
853                        Sign::Plus
854                    };
855                    FractionEnum::Exact(GenericFraction::Rational(
856                        s0 * s1,
857                        Ratio::new(
858                            UInt::from(value.0.abs() as u128),
859                            UInt::from(value.1.abs() as u128),
860                        ),
861                    ))
862                } else {
863                    FractionEnum::Approx(value.0 as f64 / value.1 as f64)
864                }
865            }
866        }
867    };
868}
869
870macro_rules! add {
871    ($t:ident) => {
872        impl<'a> Add<$t> for &'a FractionEnum {
873            type Output = FractionEnum;
874
875            fn add(self, rhs: $t) -> Self::Output {
876                let rhs = rhs.into();
877                match (self, rhs) {
878                    (FractionEnum::Exact(x), FractionEnum::Exact(y)) => {
879                        FractionEnum::Exact(x.add(y))
880                    }
881                    (FractionEnum::Approx(x), FractionEnum::Approx(y)) => {
882                        FractionEnum::Approx(x.add(y))
883                    }
884                    _ => FractionEnum::CannotCombineExactAndApprox,
885                }
886            }
887        }
888    };
889}
890
891macro_rules! add_assign {
892    ($t:ident) => {
893        impl AddAssign<$t> for FractionEnum {
894            fn add_assign(&mut self, rhs: $t) {
895                let rhs = rhs.into();
896                if self.matches(&rhs) {
897                    match (self, rhs) {
898                        (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.add_assign(y),
899                        (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.add_assign(y),
900                        _ => {}
901                    };
902                } else {
903                    *self = FractionEnum::CannotCombineExactAndApprox
904                }
905            }
906        }
907    };
908}
909
910macro_rules! sub {
911    ($t:ident) => {
912        impl<'a> Sub<$t> for &'a FractionEnum {
913            type Output = FractionEnum;
914
915            fn sub(self, rhs: $t) -> Self::Output {
916                let rhs = rhs.into();
917                match (self, rhs) {
918                    (FractionEnum::Exact(x), FractionEnum::Exact(y)) => {
919                        FractionEnum::Exact(x.sub(y))
920                    }
921                    (FractionEnum::Approx(x), FractionEnum::Approx(y)) => {
922                        FractionEnum::Approx(x.sub(y))
923                    }
924                    _ => FractionEnum::CannotCombineExactAndApprox,
925                }
926            }
927        }
928    };
929}
930
931macro_rules! sub_assign {
932    ($t:ident) => {
933        impl SubAssign<$t> for FractionEnum {
934            fn sub_assign(&mut self, rhs: $t) {
935                let rhs = rhs.into();
936                if self.matches(&rhs) {
937                    match (self, rhs) {
938                        (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.sub_assign(y),
939                        (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.sub_assign(y),
940                        _ => {}
941                    };
942                } else {
943                    *self = FractionEnum::CannotCombineExactAndApprox
944                }
945            }
946        }
947    };
948}
949
950macro_rules! mul {
951    ($t:ident) => {
952        impl<'a> Mul<$t> for &'a FractionEnum {
953            type Output = FractionEnum;
954
955            fn mul(self, rhs: $t) -> Self::Output {
956                let rhs = rhs.into();
957                match (self, rhs) {
958                    (FractionEnum::Exact(x), FractionEnum::Exact(y)) => {
959                        FractionEnum::Exact(x.mul(y))
960                    }
961                    (FractionEnum::Approx(x), FractionEnum::Approx(y)) => {
962                        FractionEnum::Approx(x.mul(y))
963                    }
964                    _ => FractionEnum::CannotCombineExactAndApprox,
965                }
966            }
967        }
968    };
969}
970
971macro_rules! mul_assign {
972    ($t:ident) => {
973        impl MulAssign<$t> for FractionEnum {
974            fn mul_assign(&mut self, rhs: $t) {
975                let rhs = rhs.into();
976                if self.matches(&rhs) {
977                    match (self, rhs) {
978                        (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.mul_assign(y),
979                        (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.mul_assign(y),
980                        _ => {}
981                    };
982                } else {
983                    *self = FractionEnum::CannotCombineExactAndApprox
984                }
985            }
986        }
987    };
988}
989
990macro_rules! div {
991    ($t:ident) => {
992        impl<'a> Div<$t> for &'a FractionEnum {
993            type Output = FractionEnum;
994
995            fn div(self, rhs: $t) -> Self::Output {
996                let rhs = rhs.into();
997                match (self, rhs) {
998                    (FractionEnum::Exact(x), FractionEnum::Exact(y)) => {
999                        FractionEnum::Exact(x.div(y))
1000                    }
1001                    (FractionEnum::Approx(x), FractionEnum::Approx(y)) => {
1002                        FractionEnum::Approx(x.div(y))
1003                    }
1004                    _ => FractionEnum::CannotCombineExactAndApprox,
1005                }
1006            }
1007        }
1008    };
1009}
1010
1011macro_rules! div_assign {
1012    ($t:ident) => {
1013        impl DivAssign<$t> for FractionEnum {
1014            fn div_assign(&mut self, rhs: $t) {
1015                let rhs = rhs.into();
1016                if self.matches(&rhs) {
1017                    match (self, rhs) {
1018                        (FractionEnum::Exact(x), FractionEnum::Exact(y)) => x.div_assign(y),
1019                        (FractionEnum::Approx(x), FractionEnum::Approx(y)) => x.div_assign(y),
1020                        _ => {}
1021                    };
1022                } else {
1023                    *self = FractionEnum::CannotCombineExactAndApprox
1024                }
1025            }
1026        }
1027    };
1028}
1029
1030macro_rules! ttype_tuple {
1031    ($t:ident) => {
1032        from_tuple_u_u!($t, usize);
1033        from_tuple_u_u!($t, u128);
1034        from_tuple_u_u!($t, u64);
1035        from_tuple_u_u!($t, u32);
1036        from_tuple_u_u!($t, u16);
1037        from_tuple_u_u!($t, u8);
1038        from_tuple_u_i!($t, i128);
1039        from_tuple_u_i!($t, i64);
1040        from_tuple_u_i!($t, i32);
1041        from_tuple_u_i!($t, i16);
1042        from_tuple_u_i!($t, i8);
1043    };
1044}
1045
1046macro_rules! ttype_tuple_signed {
1047    ($t:ident) => {
1048        from_tuple_i_u!($t, usize);
1049        from_tuple_i_u!($t, u128);
1050        from_tuple_i_u!($t, u64);
1051        from_tuple_i_u!($t, u32);
1052        from_tuple_i_u!($t, u16);
1053        from_tuple_i_u!($t, u8);
1054        from_tuple_i_i!($t, i64);
1055        from_tuple_i_i!($t, i32);
1056        from_tuple_i_i!($t, i16);
1057        from_tuple_i_i!($t, i8);
1058    };
1059}
1060
1061macro_rules! ttype {
1062    ($t:ident) => {
1063        from!($t);
1064        ttype_tuple!($t);
1065        add!($t);
1066        add_assign!($t);
1067        sub!($t);
1068        sub_assign!($t);
1069        mul!($t);
1070        mul_assign!($t);
1071        div!($t);
1072        div_assign!($t);
1073    };
1074}
1075
1076macro_rules! ttype_signed {
1077    ($t:ident) => {
1078        from_signed!($t);
1079        ttype_tuple_signed!($t);
1080        add!($t);
1081        add_assign!($t);
1082        sub!($t);
1083        sub_assign!($t);
1084        mul!($t);
1085        mul_assign!($t);
1086        div!($t);
1087        div_assign!($t);
1088    };
1089}
1090
1091ttype!(usize);
1092ttype!(u128);
1093ttype!(u64);
1094ttype!(u32);
1095ttype!(u16);
1096ttype!(u8);
1097ttype_signed!(i128);
1098ttype_signed!(i64);
1099ttype_signed!(i32);
1100ttype_signed!(i16);
1101ttype_signed!(i8);
1102
1103#[cfg(test)]
1104mod tests {
1105    use std::ops::Neg;
1106
1107    use crate::{fraction_enum::FractionEnum, traits::{One, Signed, Zero}};
1108
1109    #[test]
1110    fn fraction_neg() {
1111        let one = FractionEnum::one();
1112        assert!(one.is_positive());
1113        let one = one.neg();
1114        assert!(one.is_negative());
1115    }
1116
1117    #[test]
1118    fn fraction_exact() {
1119        let zero = FractionEnum::one().one_minus();
1120
1121        assert!(zero.is_zero());
1122    }
1123}