Skip to main content

dashu_int/
gcd_ops.rs

1//! Operators for finding greatest common divisor.
2
3use crate::{
4    helper_macros::{
5        forward_ibig_binop_to_repr, forward_ibig_ubig_binop_to_repr, forward_ubig_binop_to_repr,
6        forward_ubig_ibig_binop_to_repr,
7    },
8    ibig::IBig,
9    ubig::UBig,
10    Sign,
11};
12use dashu_base::ring::{ExtendedGcd, Gcd};
13
14forward_ubig_binop_to_repr!(impl Gcd, gcd);
15macro_rules! impl_ubig_gcd_ext {
16    ($repr0:ident, $repr1:ident) => {{
17        let (r, s, t) = $repr0.gcd_ext($repr1);
18        (UBig(r), IBig(s), IBig(t))
19    }};
20}
21forward_ubig_binop_to_repr!(
22    impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig),
23    OutputGcd = UBig, OutputCoeff = IBig,
24    impl_ubig_gcd_ext
25);
26
27macro_rules! impl_ibig_gcd {
28    ($sign0:ident, $mag0:ident, $sign1:ident, $mag1:ident) => {{
29        let _unused = ($sign0, $sign1);
30        UBig($mag0.gcd($mag1))
31    }};
32}
33macro_rules! impl_ibig_gcd_ext {
34    ($sign0:ident, $mag0:ident, $sign1:ident, $mag1:ident) => {{
35        let (r, s, t) = $mag0.gcd_ext($mag1);
36        (UBig(r), $sign0 * IBig(s), $sign1 * IBig(t))
37    }};
38}
39forward_ibig_binop_to_repr!(impl Gcd, gcd, Output = UBig, impl_ibig_gcd);
40forward_ibig_binop_to_repr!(
41    impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig),
42    OutputGcd = UBig, OutputCoeff = IBig,
43    impl_ibig_gcd_ext
44);
45
46forward_ubig_ibig_binop_to_repr!(impl Gcd, gcd, Output = UBig, impl_ibig_gcd);
47forward_ibig_ubig_binop_to_repr!(impl Gcd, gcd, Output = UBig, impl_ibig_gcd);
48forward_ubig_ibig_binop_to_repr!(impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig), OutputGcd = UBig, OutputCoeff = IBig, impl_ibig_gcd_ext);
49forward_ibig_ubig_binop_to_repr!(impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig), OutputGcd = UBig, OutputCoeff = IBig, impl_ibig_gcd_ext);
50
51mod repr {
52    use super::*;
53    use crate::{
54        add,
55        arch::word::{DoubleWord, Word},
56        buffer::Buffer,
57        cmp, div, gcd, memory,
58        memory::MemoryAllocation,
59        mul,
60        primitive::{shrink_dword, PrimitiveSigned},
61        repr::{
62            Repr,
63            TypedRepr::{self, *},
64            TypedReprRef::{self, *},
65        },
66    };
67    use core::cmp::Ordering;
68
69    impl<'l, 'r> Gcd<TypedReprRef<'r>> for TypedReprRef<'l> {
70        type Output = Repr;
71
72        fn gcd(self, rhs: TypedReprRef) -> Repr {
73            match (self, rhs) {
74                (RefSmall(dword0), RefSmall(dword1)) => Repr::from_dword(dword0.gcd(dword1)),
75                (RefSmall(dword0), RefLarge(words1)) => gcd_large_dword(words1, dword0),
76                (RefLarge(words0), RefSmall(dword1)) => gcd_large_dword(words0, dword1),
77                (RefLarge(words0), RefLarge(words1)) => gcd_large(words0.into(), words1.into()),
78            }
79        }
80    }
81
82    impl<'l> Gcd<TypedRepr> for TypedReprRef<'l> {
83        type Output = Repr;
84        #[inline]
85        fn gcd(self, rhs: TypedRepr) -> Self::Output {
86            self.gcd(rhs.as_ref())
87        }
88    }
89
90    impl<'r> Gcd<TypedReprRef<'r>> for TypedRepr {
91        type Output = Repr;
92        #[inline]
93        fn gcd(self, rhs: TypedReprRef) -> Self::Output {
94            self.as_ref().gcd(rhs)
95        }
96    }
97
98    impl Gcd<TypedRepr> for TypedRepr {
99        type Output = Repr;
100        #[inline]
101        fn gcd(self, rhs: TypedRepr) -> Self::Output {
102            self.as_ref().gcd(rhs.as_ref())
103        }
104    }
105
106    /// Perform gcd on a large number with a `DoubleWord`.
107    #[inline]
108    fn gcd_large_dword(buffer: &[Word], rhs: DoubleWord) -> Repr {
109        if rhs == 0 {
110            Repr::from_buffer(buffer.into())
111        } else if let Some(word) = shrink_dword(rhs) {
112            // reduce the large number by single word rhs
113            let rem = div::rem_by_word(buffer, word);
114            if rem == 0 {
115                Repr::from_word(word)
116            } else {
117                Repr::from_word(rem.gcd(word))
118            }
119        } else {
120            // reduce the large number by double word rhs
121            let rem = div::rem_by_dword(buffer, rhs);
122            if rem == 0 {
123                Repr::from_dword(rhs)
124            } else {
125                Repr::from_dword(rem.gcd(rhs))
126            }
127        }
128    }
129
130    /// Perform gcd on two large numbers.
131    #[inline]
132    fn gcd_large(mut lhs: Buffer, mut rhs: Buffer) -> Repr {
133        // make sure lhs > rhs
134        match cmp::cmp_in_place(&lhs, &rhs) {
135            Ordering::Greater => {}
136            Ordering::Equal => return Repr::from_buffer(lhs),
137            Ordering::Less => core::mem::swap(&mut lhs, &mut rhs),
138        };
139
140        let mut allocation =
141            MemoryAllocation::new(gcd::memory_requirement_exact(lhs.len(), rhs.len()));
142
143        let (len, swapped) = gcd::gcd_in_place(&mut lhs, &mut rhs, &mut allocation.memory());
144        if swapped {
145            rhs.truncate(len);
146            Repr::from_buffer(rhs)
147        } else {
148            lhs.truncate(len);
149            Repr::from_buffer(lhs)
150        }
151    }
152
153    impl<'l, 'r> ExtendedGcd<TypedReprRef<'r>> for TypedReprRef<'l> {
154        type OutputCoeff = Repr;
155        type OutputGcd = Repr;
156
157        fn gcd_ext(self, rhs: TypedReprRef<'r>) -> (Repr, Repr, Repr) {
158            match (self, rhs) {
159                (RefSmall(dword0), RefSmall(dword1)) => gcd_ext_dword(dword0, dword1),
160                (RefLarge(words0), RefSmall(dword1)) => gcd_ext_large_dword(words0.into(), dword1),
161                (RefSmall(dword0), RefLarge(words1)) => {
162                    let (g, s, t) = gcd_ext_large_dword(words1.into(), dword0);
163                    (g, t, s)
164                }
165                (RefLarge(words0), RefLarge(words1)) => gcd_ext_large(words0.into(), words1.into()),
166            }
167        }
168    }
169
170    impl<'r> ExtendedGcd<TypedReprRef<'r>> for TypedRepr {
171        type OutputCoeff = Repr;
172        type OutputGcd = Repr;
173
174        fn gcd_ext(self, rhs: TypedReprRef<'r>) -> (Repr, Repr, Repr) {
175            match (self, rhs) {
176                (Small(dword0), RefSmall(dword1)) => gcd_ext_dword(dword0, dword1),
177                (Large(buffer0), RefSmall(dword1)) => gcd_ext_large_dword(buffer0, dword1),
178                (Small(dword0), RefLarge(words1)) => {
179                    let (g, s, t) = gcd_ext_large_dword(words1.into(), dword0);
180                    (g, t, s)
181                }
182                (Large(buffer0), RefLarge(words1)) => gcd_ext_large(buffer0, words1.into()),
183            }
184        }
185    }
186
187    impl<'l> ExtendedGcd<TypedRepr> for TypedReprRef<'l> {
188        type OutputCoeff = Repr;
189        type OutputGcd = Repr;
190
191        fn gcd_ext(self, rhs: TypedRepr) -> (Repr, Repr, Repr) {
192            match (self, rhs) {
193                (RefSmall(dword0), Small(dword1)) => gcd_ext_dword(dword0, dword1),
194                (RefLarge(words0), Small(dword1)) => gcd_ext_large_dword(words0.into(), dword1),
195                (RefSmall(dword0), Large(buffer1)) => {
196                    let (g, s, t) = gcd_ext_large_dword(buffer1, dword0);
197                    (g, t, s)
198                }
199                (RefLarge(words0), Large(buffer1)) => gcd_ext_large(words0.into(), buffer1),
200            }
201        }
202    }
203
204    impl ExtendedGcd<TypedRepr> for TypedRepr {
205        type OutputCoeff = Repr;
206        type OutputGcd = Repr;
207
208        #[inline]
209        fn gcd_ext(self, rhs: TypedRepr) -> (Repr, Repr, Repr) {
210            match (self, rhs) {
211                (Small(dword0), Small(dword1)) => gcd_ext_dword(dword0, dword1),
212                (Large(buffer0), Small(dword1)) => gcd_ext_large_dword(buffer0, dword1),
213                (Small(dword0), Large(buffer1)) => {
214                    let (g, s, t) = gcd_ext_large_dword(buffer1, dword0);
215                    (g, t, s)
216                }
217                (Large(buffer0), Large(buffer1)) => gcd_ext_large(buffer0, buffer1),
218            }
219        }
220    }
221
222    #[inline]
223    fn gcd_ext_dword(lhs: DoubleWord, rhs: DoubleWord) -> (Repr, Repr, Repr) {
224        let (g, s, t) = lhs.gcd_ext(rhs);
225        let (s_sign, s_mag) = s.to_sign_magnitude();
226        let (t_sign, t_mag) = t.to_sign_magnitude();
227        (
228            Repr::from_dword(g),
229            Repr::from_dword(s_mag).with_sign(s_sign),
230            Repr::from_dword(t_mag).with_sign(t_sign),
231        )
232    }
233
234    /// Perform extended gcd on a large number with a `Word`.
235    #[inline]
236    fn gcd_ext_large_dword(mut buffer: Buffer, rhs: DoubleWord) -> (Repr, Repr, Repr) {
237        if rhs == 0 {
238            (Repr::from_buffer(buffer), Repr::one(), Repr::zero())
239        } else if let Some(word) = shrink_dword(rhs) {
240            // reduce the large number by single word rhs
241            let (g, a, b_sign) = gcd::gcd_ext_word(&mut buffer, word);
242            let (a_sign, a_mag) = a.to_sign_magnitude();
243            (
244                Repr::from_word(g),
245                Repr::from_word(a_mag).with_sign(a_sign),
246                Repr::from_buffer(buffer).with_sign(b_sign),
247            )
248        } else {
249            let (g, a, b_sign) = gcd::gcd_ext_dword(&mut buffer, rhs);
250            let (a_sign, a_mag) = a.to_sign_magnitude();
251            (
252                Repr::from_dword(g),
253                Repr::from_dword(a_mag).with_sign(a_sign),
254                Repr::from_buffer(buffer).with_sign(b_sign),
255            )
256        }
257    }
258
259    /// Perform extended gcd on two large numbers.
260    #[inline]
261    fn gcd_ext_large(mut lhs: Buffer, mut rhs: Buffer) -> (Repr, Repr, Repr) {
262        // make sure lhs > rhs
263        let swapped = match cmp::cmp_in_place(&lhs, &rhs) {
264            Ordering::Greater => false,
265            Ordering::Equal => return (Repr::from_buffer(lhs), Repr::one(), Repr::zero()),
266            Ordering::Less => {
267                core::mem::swap(&mut lhs, &mut rhs);
268                true
269            }
270        };
271        let (lhs_len, rhs_len) = (lhs.len(), rhs.len());
272
273        // allocate memory
274        let clone_mem = memory::array_layout::<Word>(lhs_len + rhs_len);
275        let gcd_mem = gcd::memory_requirement_ext_exact(lhs_len, rhs_len);
276        let post_mem = memory::add_layout(
277            // temporary space to store residue
278            memory::array_layout::<Word>(lhs_len + rhs_len),
279            memory::max_layout(
280                // memory required for post processing: one multiplication + one division
281                mul::memory_requirement_exact(lhs_len + rhs_len, rhs_len),
282                div::memory_requirement_exact(lhs_len + rhs_len + 1, rhs_len),
283            ),
284        );
285        let mut allocation = MemoryAllocation::new(memory::add_layout(
286            clone_mem,
287            memory::max_layout(gcd_mem, post_mem),
288        ));
289        let mut memory = allocation.memory();
290
291        // copy oprands for post processing
292        let (lhs_clone, mut memory) = memory.allocate_slice_copy(&lhs);
293        let (rhs_clone, mut memory) = memory.allocate_slice_copy(&rhs);
294
295        // actual computation
296        let (g_len, b_len, b_sign) = gcd::gcd_ext_in_place(&mut lhs, &mut rhs, &mut memory);
297
298        // the result from the internal function is g = gcd(lhs, rhs), b s.t g = b*rhs mod lhs
299        // post processing: a = (g - rhs * b) / lhs
300        rhs.truncate(g_len);
301        let g = rhs;
302        lhs.truncate(b_len);
303        let b = lhs;
304
305        // residue = g - rhs * b
306        let brhs_len = rhs_clone.len() + b.len();
307        let (residue, mut memory) = memory.allocate_slice_fill(brhs_len + 1, 0);
308        mul::multiply(&mut residue[..brhs_len], rhs_clone, &b, &mut memory);
309        match b_sign {
310            Sign::Negative => {
311                *residue.last_mut().unwrap() = add::add_in_place(residue, &g) as Word;
312            }
313            Sign::Positive => {
314                let overflow = add::sub_in_place(residue, &g);
315                debug_assert!(!overflow);
316            }
317        };
318
319        // a = residue / lhs
320        let (shift, fast_div_top) = div::normalize(lhs_clone);
321        let overflow =
322            div::div_rem_unshifted_in_place(residue, lhs_clone, shift, fast_div_top, &mut memory);
323        let mut a = Buffer::from(&residue[lhs_len..]);
324        debug_assert_eq!(residue[0], 0); // this division is an exact division
325        if overflow > 0 {
326            a.push(overflow);
327        }
328
329        let g = Repr::from_buffer(g);
330        let a = Repr::from_buffer(a).with_sign(-b_sign);
331        let b = Repr::from_buffer(b).with_sign(b_sign);
332        if swapped {
333            (g, b, a)
334        } else {
335            (g, a, b)
336        }
337    }
338}