Skip to main content

yui_core/conc/num/
ratio.rs

1//! Field of fractions over a Euclidean ring: `Ratio<T>` represents `numer / denom`.
2//!
3//! With `T = i64` or `BigInt` this gives the rationals ℚ. More generally,
4//! `Ratio<T>` is a [`Field`] whenever `T` is a [`EucRing`].
5//!
6//! See: <https://en.wikipedia.org/wiki/Field_of_fractions>,
7//! <https://en.wikipedia.org/wiki/Rational_number>
8
9use std::fmt::{Display, Debug};
10use std::str::FromStr;
11use std::cmp;
12use std::ops::{Mul, Add, Sub, Neg, AddAssign, SubAssign, MulAssign, Div, DivAssign, Rem, RemAssign};
13use num_traits::{Zero, One};
14use auto_impl_ops::auto_ops;
15
16use crate::abst::{EucRing, EucRingOps, MathType, Mon, AddMon, AddGrp, AddMonOps, AddGrpOps, MonOps, RingOps, Ring, FieldOps, Field};
17use crate::util::parse_err::ParseErr;
18use super::int::{IntType, IntOps};
19
20/// A fraction `numer / denom` over a [`EucRing`] `T`, kept in reduced form.
21#[derive(Copy, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]
23pub struct Ratio<T> {
24    numer: T,
25    denom: T,
26}
27
28impl<T> Ratio<T> {
29    #[inline]
30    const fn new_raw(numer: T, denom: T) -> Ratio<T> {
31        Ratio { numer, denom }
32    }
33
34    #[inline]
35    pub const fn numer(&self) -> &T {
36        &self.numer
37    }
38
39    #[inline]
40    pub const fn denom(&self) -> &T {
41        &self.denom
42    }
43}
44
45impl<T> Ratio<T>
46where T: EucRing, for<'x> &'x T: EucRingOps<T> {
47    #[inline]
48    pub fn new(numer: T, denom: T) -> Ratio<T> {
49        assert!(!denom.is_zero());
50
51        let mut ret = Ratio::new_raw(numer, denom);
52        ret.reduce();
53        ret
54    }
55
56    fn reduce(&mut self) {
57        if self.numer.is_zero() {
58            if !self.denom.is_one() {
59                self.denom.set_one();
60            }
61            return;
62        }
63
64        let u = self.denom.normalizing_unit();
65
66        if !u.is_one() {
67            self.numer *= &u;
68            self.denom *= &u;
69        }
70
71        if self.denom.is_one() || self.numer.is_unit() {
72            return
73        }
74
75        let g = EucRing::gcd(&self.numer, &self.denom); // normalized
76
77        if !g.is_one() {
78            self.numer /= &g;
79            self.denom /= &g;
80        }
81    }
82
83    pub fn is_int(&self) -> bool {
84        self.denom.is_one()
85    }
86}
87
88impl<T> From<T> for Ratio<T>
89where T: One {
90    fn from(a: T) -> Self {
91        Self::new_raw(a, T::one())
92    }
93}
94
95impl<T> From<(T, T)> for Ratio<T>
96where T: EucRing, for<'x> &'x T: EucRingOps<T> {
97    fn from(pair: (T, T)) -> Self {
98        let (p, q) = pair;
99        Self::new(p, q)
100    }
101}
102
103impl<T> FromStr for Ratio<T>
104where T: EucRing + FromStr, for<'x> &'x T: EucRingOps<T> {
105    type Err = ParseErr;
106
107    fn from_str(s: &str) -> Result<Self, Self::Err> {
108        if let Ok(a) = s.parse::<T>() {
109            return Ok(Self::from(a))
110        }
111
112        let r = regex::Regex::new(r"(.+)/(.+)").unwrap();
113        if let Some(c) = r.captures(s) {
114            let (s1, s2) = (&c[1], &c[2]);
115            if let (Ok(a), Ok(b)) = (s1.parse::<T>(), s2.parse::<T>()) {
116                if b.is_zero() {
117                    return Err(ParseErr::new(format!("zero denominator in \"{s}\"")))
118                }
119                return Ok(Self::new(a, b))
120            }
121        }
122
123        Err(ParseErr::invalid(s, &Self::math_symbol()))
124    }
125}
126
127impl<T> Default for Ratio<T>
128where T: Default + One {
129    fn default() -> Self {
130        Self::from(T::default())
131    }
132}
133
134impl<T> Display for Ratio<T>
135where T: Display {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        use crate::util::format::paren_expr;
138
139        let p = paren_expr(&self.numer);
140        let q = paren_expr(&self.denom);
141
142        if &q == "1" {
143            write!(f, "{}", p)
144        } else {
145            write!(f, "{}/{}", p, q)
146        }
147    }
148}
149
150impl<T> Debug for Ratio<T>
151where T: Display {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        Display::fmt(&self, f)
154    }
155}
156
157impl<T> Zero for Ratio<T>
158where T: EucRing, for<'x> &'x T: EucRingOps<T> {
159    fn zero() -> Self {
160        Self::from(T::zero())
161    }
162
163    fn is_zero(&self) -> bool {
164        self.numer.is_zero()
165    }
166}
167
168impl<T> One for Ratio<T>
169where T: EucRing, for<'x> &'x T: EucRingOps<T> {
170    fn one() -> Self {
171        Self::from(T::one())
172    }
173
174    fn is_one(&self) -> bool {
175        self.numer == self.denom
176    }
177}
178
179macro_rules! impl_add_assign_op {
180    ($trait:ident, $method:ident) => {
181        #[auto_ops]
182        impl<T> $trait<&Ratio<T>> for Ratio<T>
183        where T: EucRing, for<'x> &'x T: EucRingOps<T> {
184            fn $method(&mut self, rhs: &Ratio<T>) {
185                let (_, b) = (&self.numer, &self.denom);
186                let (c, d) = ( &rhs.numer,  &rhs.denom);
187
188                if rhs.is_zero() {
189                    // do nothing
190                } else if self.is_zero() {
191                    self.numer.$method(c);  // 0 -> 0 ± c
192                    self.denom = d.clone(); // 1 -> d
193                } else if b == d {
194                    self.numer.$method(c);  // a -> a ± c
195                    self.reduce()
196                } else {
197                    let l = EucRing::lcm(b, d); // l = xb = yd
198                    self.numer *= (&l / b);     // a -> xa ± yc
199                    self.numer.$method((&l / d) * c);
200                    self.denom = l;             // b -> l
201                    self.reduce()
202                }
203            }
204        }
205    };
206}
207
208impl_add_assign_op!(AddAssign, add_assign);
209impl_add_assign_op!(SubAssign, sub_assign);
210
211impl<T> Neg for Ratio<T>
212where T: EucRing, for<'x> &'x T: EucRingOps<T> {
213    type Output = Self;
214    fn neg(self) -> Self::Output {
215        Ratio::new(-&self.numer, self.denom)
216    }
217}
218
219impl<T> Neg for &Ratio<T>
220where T: EucRing, for<'x> &'x T: EucRingOps<T> {
221    type Output = Ratio<T>;
222    fn neg(self) -> Self::Output {
223        Ratio::new(-&self.numer, self.denom.clone())
224    }
225}
226
227#[auto_ops]
228impl<T> MulAssign<&Ratio<T>> for Ratio<T>
229where T: EucRing, for<'x> &'x T: EucRingOps<T> {
230    fn mul_assign(&mut self, rhs: &Ratio<T>) {
231        let (a, b) = (&self.numer, &self.denom);
232        let (c, d) = ( &rhs.numer,  &rhs.denom);
233
234        if self.is_zero() || rhs.is_one() {
235            // do nothing
236        } else if rhs.is_zero() {
237            self.set_zero();             // a -> 0, b -> 1
238        } else if rhs.is_int() {
239            let k = EucRing::gcd(b, c);  // b = kb', c = kc'
240            self.numer *= c / &k;        // a -> a * c'
241            self.denom /= &k;            // b -> b'
242        } else if self.is_int() {
243            let k = EucRing::gcd(a, d);  // a = ka', d = kd'
244            self.numer /= &k;            // a -> a' * c
245            self.numer *= c;             //
246            self.denom = d / &k;         // 1 ->      d'
247        } else {
248            let k = EucRing::gcd(a, d);  // a = ka', d = kd'
249            let l = EucRing::gcd(b, c);  // b = lb', c = lc'
250            self.numer /= &k;            // a -> a' * c'
251            self.numer *= c / &l;        //
252            self.denom /= &l;            // b -> b' * d'
253            self.denom *= d / &k;        //
254        }
255    }
256}
257
258#[auto_ops]
259impl<T> DivAssign<&Ratio<T>> for Ratio<T>
260where T: EucRing, for<'x> &'x T: EucRingOps<T> {
261    fn div_assign(&mut self, rhs: &Ratio<T>) {
262        assert!(!rhs.is_zero());
263        *self *= rhs.inv().unwrap()
264    }
265}
266
267#[auto_ops]
268impl<T> Rem<&Ratio<T>> for &Ratio<T>
269where T: EucRing, for<'x> &'x T: EucRingOps<T> {
270    type Output = Ratio<T>;
271    fn rem(self, rhs: &Ratio<T>) -> Self::Output {
272        assert!(!rhs.is_zero());
273        Ratio::zero() // MEMO Frac<T> is a field.
274    }
275}
276
277macro_rules! decl_alg_ops {
278    ($trait:ident) => {
279        impl<T> $trait for Ratio<T>
280        where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
281
282        impl<T> $trait<Ratio<T>> for &Ratio<T>
283        where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
284    };
285}
286
287decl_alg_ops!(AddMonOps);
288decl_alg_ops!(AddGrpOps);
289decl_alg_ops!(MonOps);
290decl_alg_ops!(RingOps);
291decl_alg_ops!(EucRingOps);
292decl_alg_ops!(FieldOps);
293
294impl<T> MathType for Ratio<T>
295where T: EucRing, for<'x> &'x T: EucRingOps<T> {
296    fn math_symbol() -> String {
297        let t = T::math_symbol();
298        if &t == "Z" {
299            String::from("Q")
300        } else {
301            format!("Q({})", T::math_symbol())
302        }
303    }
304}
305
306impl<T> Mon for Ratio<T>
307where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
308
309impl<T> AddMon for Ratio<T>
310where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
311
312impl<T> AddGrp for Ratio<T>
313where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
314
315impl<T> Ring for Ratio<T>
316where T: EucRing, for<'x> &'x T: EucRingOps<T> {
317    fn inv(&self) -> Option<Self> {
318        if self.is_zero() {
319            None
320        } else {
321            let inv = Self::new(self.denom.clone(), self.numer.clone());
322            Some(inv)
323        }
324    }
325
326    fn is_unit(&self) -> bool {
327        !self.is_zero()
328    }
329
330    fn normalizing_unit(&self) -> Self {
331        if self.is_zero() {
332            Self::one()
333        } else {
334            self.inv().unwrap()
335        }
336    }
337
338    fn c_weight(&self) -> f64 {
339        f64::max(self.numer.c_weight(), self.denom.c_weight())
340    }
341}
342
343impl<T> EucRing for Ratio<T>
344where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
345
346impl<T> Field for Ratio<T>
347where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
348
349impl<T> Ratio<T>
350where T: IntType, for<'x> &'x T: IntOps<T> {
351    pub fn abs(&self) -> Self {
352        if self.numer.is_negative() {
353            -self
354        } else {
355            self.clone()
356        }
357    }
358
359    pub fn to_f64(&self) -> f64 {
360        let p = self.numer.to_f64().unwrap();
361        let q = self.denom.to_f64().unwrap();
362        p / q
363    }
364}
365
366impl<T> Ord for Ratio<T>
367where T: IntType, for<'x> &'x T: IntOps<T> {
368    fn cmp(&self, other: &Self) -> cmp::Ordering {
369        // `reduce` keeps denominators positive, so cross-multiplying preserves the order.
370        (self.numer() * other.denom()).cmp(&(other.numer() * self.denom()))
371    }
372}
373
374impl<T> PartialOrd for Ratio<T>
375where T: IntType, for<'x> &'x T: IntOps<T> {
376    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
377        Some(self.cmp(other))
378    }
379}
380
381mod tex {
382    use crate::util::tex::TeX;
383    use super::*;
384
385    impl<T> TeX for Ratio<T>
386    where T: TeX + MathType {
387        fn tex_math_symbol() -> String {
388            let t = T::math_symbol();
389            if &t == "Z" {
390                String::from("\\mathbb{Q}")
391            } else {
392                format!("Q({})", T::math_symbol())
393            }
394        }
395
396        fn tex_string(&self) -> String {
397            let p = self.numer.tex_string();
398            let q = self.denom.tex_string();
399
400            if &q == "1" {
401                p
402            } else if !p.starts_with('-') && !p.contains(' ') {
403                format!(r"\frac{{{p}}}{{{q}}}")
404            } else {
405                let p = p.strip_prefix('-').unwrap();
406                format!(r"-\frac{{{p}}}{{{q}}}")
407            }
408        }
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415
416    #[test]
417    fn math_symbol() {
418        assert_eq!(Ratio::<i32>::math_symbol(), "Q");
419    }
420
421    #[test]
422    fn constants() {
423        assert_eq!(Ratio::zero(), Ratio::new_raw(0, 1));
424        assert_eq!(Ratio::one(),  Ratio::new_raw(1, 1));
425    }
426
427    #[test]
428    fn reduce() {
429        let a = Ratio::new(0, -4);
430        assert_eq!(a.numer, 0);
431        assert_eq!(a.denom, 1);
432
433        let a = Ratio::new(-3, 1);
434        assert_eq!(a.numer, -3);
435        assert_eq!(a.denom, 1);
436
437        let a = Ratio::new(1, -3);
438        assert_eq!(a.numer, -1);
439        assert_eq!(a.denom, 3);
440
441        let a = Ratio::new(6, -8);
442        assert_eq!(a.numer, -3);
443        assert_eq!(a.denom, 4);
444    }
445
446    #[test]
447    fn display() {
448        assert_eq!(format!("{}", Ratio::new(-3, 1)), "-3");
449        assert_eq!(format!("{}", Ratio::new(-3, 4)), "-3/4");
450    }
451
452    #[test]
453    fn debug() {
454        assert_eq!(format!("{:?}", Ratio::new(-3, 1)), "-3");
455        assert_eq!(format!("{:?}", Ratio::new(-3, 4)), "-3/4");
456    }
457
458    #[test]
459    fn add() {
460        let a = Ratio::new(1, 2);
461        let b = Ratio::new(3, 5);
462        assert_eq!(a + b, Ratio::new(11, 10));
463
464        let a = Ratio::new(1, 2);
465        let o = Ratio::zero();
466        assert_eq!(a + o, a);
467        assert_eq!(o + a, a);
468
469        let a = Ratio::new(1, 3);
470        let b = Ratio::new(2, 3);
471        assert_eq!(a + b, Ratio::new(1, 1));
472
473        let a = Ratio::new(1, 6);
474        let b = Ratio::new(1, 3);
475        assert_eq!(a + b, Ratio::new(1, 2));
476    }
477
478    #[test]
479    fn add_assign() {
480        let mut a = Ratio::new(1, 2);
481        a += Ratio::new(3, 5);
482
483        assert_eq!(a, Ratio::new(11, 10));
484    }
485
486    #[test]
487    fn neg() {
488        let a = Ratio::new(1, 2);
489        assert_eq!(-a, Ratio::new(-1, 2));
490    }
491
492    #[test]
493    fn sub() {
494        let a = Ratio::new(1, 2);
495        let b = Ratio::new(3, 5);
496        assert_eq!(a - b, Ratio::new(-1, 10));
497
498        let a = Ratio::new(1, 2);
499        let o = Ratio::zero();
500        assert_eq!(a - o, a);
501        assert_eq!(o - a, -a);
502    }
503
504    #[test]
505    fn sub_assign() {
506        let mut a = Ratio::new(1, 2);
507        a -= Ratio::new(3, 5);
508        assert_eq!(a, Ratio::new(-1, 10));
509    }
510
511    #[test]
512    fn mul() {
513        let a = Ratio::new(3, 10);
514        let b = Ratio::new(-2, 7);
515        assert_eq!(a * b, Ratio::new(-3, 35));
516
517        let a = Ratio::new(3, 4);
518        let e = Ratio::one();
519        assert_eq!(a * e, a);
520        assert_eq!(e * a, a);
521
522        let a = Ratio::new(3, 4);
523        let e = -Ratio::one();
524        assert_eq!(a * e, -a);
525        assert_eq!(e * a, -a);
526
527        let a = Ratio::new(3, 4);
528        let o = Ratio::zero();
529        assert_eq!(a * o, Ratio::zero());
530        assert_eq!(o * a, Ratio::zero());
531    }
532
533    #[test]
534    fn mul_assign() {
535        let mut a = Ratio::new(3, 10);
536        a *= Ratio::new(2, 7);
537        assert_eq!(a, Ratio::new(3, 35));
538    }
539
540    #[test]
541    fn div() {
542        let a = Ratio::new(3, 10);
543        let b = Ratio::new(2, 7);
544        assert_eq!(a / b, Ratio::new(21, 20));
545    }
546
547    #[test]
548    fn div_assign() {
549        let mut a = Ratio::new(3, 10);
550        a /= Ratio::new(2, 7);
551        assert_eq!(a, Ratio::new(21, 20));
552    }
553
554    #[test]
555    fn rem() {
556        let a = Ratio::new(3, 10);
557        let b = Ratio::new(2, 7);
558        assert_eq!(a % b, Ratio::zero());
559    }
560
561    #[test]
562    fn rem_assign() {
563        let mut a = Ratio::new(3, 10);
564        a %= Ratio::new(2, 7);
565        assert_eq!(a, Ratio::zero());
566    }
567
568    #[test]
569    fn inv() {
570        let a = Ratio::new(-3, 10);
571        assert_eq!(a.inv(), Some(Ratio::new(-10, 3)));
572
573        let a = Ratio::<i32>::zero();
574        assert_eq!(a.inv(), None);
575    }
576
577    #[test]
578    fn is_unit() {
579        let a = Ratio::new(-3, 10);
580        assert!(a.is_unit());
581
582        let a = Ratio::<i32>::zero();
583        assert!(!a.is_unit());
584    }
585
586    #[test]
587    fn normalizing_unit() {
588        let a = Ratio::new(-3, 10);
589        assert_eq!(a.normalizing_unit(), Ratio::new(-10, 3));
590
591        let a = Ratio::<i32>::zero();
592        assert_eq!(a.normalizing_unit(), Ratio::one());
593    }
594
595    #[test]
596    fn gcd_normalized() {
597        // over a field every nonzero element divides every other, so the gcd normalizes to 1.
598        let (x, y) = (Ratio::new(-3, 2), Ratio::new(5, 4));
599
600        let d = EucRing::gcd(&x, &y);
601        assert_eq!(d, Ratio::one(), "gcd is not normalized");
602
603        let (d, s, t) = EucRing::gcdx(&x, &y);
604        assert_eq!(d, Ratio::one(), "gcdx's d is not normalized");
605        assert_eq!(s * x + t * y, d, "Bezout fails");
606        assert_eq!(EucRing::gcd(&x, &y), d, "gcd disagrees with gcdx");
607    }
608
609    #[test]
610    fn cmp() {
611        let a = Ratio::new(3, 5);
612        let b = Ratio::new(4, 7);
613        assert!(a > b);
614    }
615
616    #[test]
617    fn from_str_zero_denom() {
618        // `Ratio` derives `DeserializeFromStr`, so this must not abort the process.
619        assert!(Ratio::<i64>::from_str("1/0").is_err());
620        assert!(Ratio::<i64>::from_str("0/0").is_err());
621        assert_eq!(Ratio::<i64>::from_str("1/2"), Ok(Ratio::new(1, 2)));
622    }
623
624    #[test]
625    fn cmp_large() {
626        // distinct values must compare distinct, however large the terms.
627        let a = Ratio::new((1i64 << 53) + 1, 1);
628        let b = Ratio::new(1i64 << 53, 1);
629        assert_ne!(a, b);
630        assert!(a > b);
631
632        let c = Ratio::new(1i64, (1i64 << 53) + 1);
633        let d = Ratio::new(1i64, 1i64 << 53);
634        assert_ne!(c, d);
635        assert!(c < d);
636    }
637
638    #[test]
639    fn c_weight() {
640        let a = Ratio::new(-43, 31);
641        assert_eq!(a.c_weight(), 43_f64);
642
643        let a = Ratio::new(34, 123);
644        assert_eq!(a.c_weight(), 123_f64);
645    }
646
647    #[test]
648    #[cfg(feature = "serde")]
649    fn serialize() {
650        let a = Ratio::new(3, 5);
651
652        let ser = serde_json::to_string(&a).unwrap();
653        assert_eq!(ser, "\"3/5\"");
654
655        let deser = serde_json::from_str::<Ratio<i32>>(&ser).unwrap();
656        assert_eq!(a, deser);
657    }
658
659    #[test]
660    fn tex() {
661        use crate::util::tex::TeX;
662        assert_eq!(Ratio::<i32>::tex_math_symbol(), "\\mathbb{Q}");
663
664        let a = Ratio::new(43, 1);
665        let b = Ratio::new(43, 31);
666        let c = Ratio::new(-43, 31);
667
668        assert_eq!(a.tex_string(), "43");
669        assert_eq!(b.tex_string(), r"\frac{43}{31}");
670        assert_eq!(c.tex_string(), r"-\frac{43}{31}");
671    }
672}