Skip to main content

yui_core/abst/
euc_ring.rs

1//! Euclidean ring (a.k.a. Euclidean domain): a [`Ring`] equipped with division `/`
2//! and remainder `%`, supporting Euclidean algorithms (gcd, extended gcd, lcm).
3//!
4//! The name is "EucRing" here rather than the more standard "Euclidean domain"
5//! to make the trait hierarchy `EucRing: Ring` immediately visible.
6//!
7//! See: <https://en.wikipedia.org/wiki/Euclidean_domain>
8
9use std::ops::{Div, DivAssign, Rem, RemAssign};
10use crate::abst::{Ring, RingOps};
11
12/// Helper trait extending [`RingOps`] with `Div` and `Rem` reference variants
13/// so [`EucRing`] can require them via one HRTB.
14pub trait EucRingOps<T = Self>:
15    RingOps<T> +
16    Div<T, Output = T> +
17    for<'a> Div<&'a T, Output = T> +
18    Rem<T, Output = T> +
19    for<'a> Rem<&'a T, Output = T> +
20{}
21
22/// A Euclidean ring: a [`Ring`] with division `/` and remainder `%`
23/// satisfying the Euclidean property — i.e. for any `x, y` with `y ≠ 0`,
24/// `x = (x / y) · y + (x % y)` with `x % y` strictly "smaller" than `y`.
25///
26/// See: <https://en.wikipedia.org/wiki/Euclidean_domain>
27pub trait EucRing:
28    Ring +
29    EucRingOps +
30    DivAssign +
31    for<'a> DivAssign<&'a Self> +
32    RemAssign +
33    for<'a> RemAssign<&'a Self>
34where
35    for<'a> &'a Self: EucRingOps<Self>,
36{
37    /// `true` iff `self` divides `y` (and `self ≠ 0`).
38    fn divides(&self, y: &Self) -> bool {
39        !self.is_zero() && (y % self).is_zero()
40    }
41
42    /// Greatest common divisor, returned in normalized form.
43    ///
44    /// `gcd(0, 0) = 0`.
45    ///
46    /// See: <https://en.wikipedia.org/wiki/Euclidean_algorithm>
47    fn gcd(x: &Self, y: &Self) -> Self {
48        if x.is_zero() && y.is_zero() { return Self::zero() }
49        if x.divides(y) { return x.normalized() }
50        if y.divides(x) { return y.normalized() }
51
52        let (mut x, mut y) = (x.clone(), y.clone());
53
54        while !y.is_zero() {
55            let r = &x % &y;
56            (x, y) = (y, r);
57        }
58
59        x.into_normalized()
60    }
61
62    /// Extended gcd: returns `(d, s, t)` such that `d = gcd(x, y) = s·x + t·y`,
63    /// with `d` normalized.
64    ///
65    /// See: <https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm>
66    fn gcdx(x: &Self, y: &Self) -> (Self, Self, Self) {
67        if x.is_zero() && y.is_zero() { return (Self::zero(), Self::zero(), Self::zero()) }
68
69        // `d` is normalized, so the cofactor is the normalizing unit rather than `1`.
70        if x.divides(y) {
71            let u = x.normalizing_unit();
72            return (x * &u, u, Self::zero())
73        }
74        if y.divides(x) {
75            let u = y.normalizing_unit();
76            return (y * &u, Self::zero(), u)
77        }
78
79        let (mut x,  mut y)  = (x.clone(), y.clone());
80        let (mut s0, mut s1) = (Self::one(),  Self::zero());
81        let (mut t0, mut t1) = (Self::zero(), Self::one() );
82
83        while !y.is_zero() {
84            let q = &x / &y;
85            let r = &x % &y;
86
87            (x, y) = (y, r);
88            (s1, s0) = (s0 - &q * &s1, s1);
89            (t1, t0) = (t0 - &q * &t1, t1);
90        }
91
92        let (d, s, t) = (x, s0, t0);
93
94        let u = d.normalizing_unit();
95        match u.is_one() {
96            true  => (d, s, t),
97            false => (d * &u, s * &u, t * &u)
98        }
99    }
100
101    /// Least common multiple, returned in normalized form.
102    fn lcm(x: &Self, y: &Self) -> Self {
103        if x.is_zero() || y.is_zero() { return Self::zero() }
104
105        let g = Self::gcd(x, y);
106        let m = x * (y / g);
107        m.into_normalized()
108    }
109}