Skip to content

Commit fc93e11

Browse files
bors[bot]strakecuviper
committed
Merge #19
19: Define extended GCD, combined GCD+LCM r=cuviper a=strake Co-authored-by: M Farkas-Dyck <[email protected]> Co-authored-by: Josh Stone <[email protected]>
2 parents 7b49eca + 10fd5e8 commit fc93e11

File tree

1 file changed

+204
-7
lines changed

1 file changed

+204
-7
lines changed

src/lib.rs

Lines changed: 204 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ extern crate std;
2121

2222
extern crate num_traits as traits;
2323

24+
use core::mem;
2425
use core::ops::Add;
2526

2627
use traits::{Num, Signed, Zero};
@@ -120,6 +121,89 @@ pub trait Integer: Sized + Num + PartialOrd + Ord + Eq {
120121
/// ~~~
121122
fn lcm(&self, other: &Self) -> Self;
122123

124+
/// Greatest Common Divisor (GCD) and
125+
/// Lowest Common Multiple (LCM) together.
126+
///
127+
/// Potentially more efficient than calling `gcd` and `lcm`
128+
/// individually for identical inputs.
129+
///
130+
/// # Examples
131+
///
132+
/// ~~~
133+
/// # use num_integer::Integer;
134+
/// assert_eq!(10.gcd_lcm(&4), (2, 20));
135+
/// assert_eq!(8.gcd_lcm(&9), (1, 72));
136+
/// ~~~
137+
#[inline]
138+
fn gcd_lcm(&self, other: &Self) -> (Self, Self) {
139+
(self.gcd(other), self.lcm(other))
140+
}
141+
142+
/// Greatest common divisor and Bézout coefficients.
143+
///
144+
/// # Examples
145+
///
146+
/// ~~~
147+
/// # extern crate num_integer;
148+
/// # extern crate num_traits;
149+
/// # fn main() {
150+
/// # use num_integer::{ExtendedGcd, Integer};
151+
/// # use num_traits::NumAssign;
152+
/// fn check<A: Copy + Integer + NumAssign>(a: A, b: A) -> bool {
153+
/// let ExtendedGcd { gcd, x, y, .. } = a.extended_gcd(&b);
154+
/// gcd == x * a + y * b
155+
/// }
156+
/// assert!(check(10isize, 4isize));
157+
/// assert!(check(8isize, 9isize));
158+
/// # }
159+
/// ~~~
160+
#[inline]
161+
fn extended_gcd(&self, other: &Self) -> ExtendedGcd<Self>
162+
where
163+
Self: Clone,
164+
{
165+
let mut s = (Self::zero(), Self::one());
166+
let mut t = (Self::one(), Self::zero());
167+
let mut r = (other.clone(), self.clone());
168+
169+
while !r.0.is_zero() {
170+
let q = r.1.clone() / r.0.clone();
171+
let f = |mut r: (Self, Self)| {
172+
mem::swap(&mut r.0, &mut r.1);
173+
r.0 = r.0 - q.clone() * r.1.clone();
174+
r
175+
};
176+
r = f(r);
177+
s = f(s);
178+
t = f(t);
179+
}
180+
181+
if r.1 >= Self::zero() {
182+
ExtendedGcd {
183+
gcd: r.1,
184+
x: s.1,
185+
y: t.1,
186+
_hidden: (),
187+
}
188+
} else {
189+
ExtendedGcd {
190+
gcd: Self::zero() - r.1,
191+
x: Self::zero() - s.1,
192+
y: Self::zero() - t.1,
193+
_hidden: (),
194+
}
195+
}
196+
}
197+
198+
/// Greatest common divisor, least common multiple, and Bézout coefficients.
199+
#[inline]
200+
fn extended_gcd_lcm(&self, other: &Self) -> (ExtendedGcd<Self>, Self)
201+
where
202+
Self: Clone + Signed,
203+
{
204+
(self.extended_gcd(other), self.lcm(other))
205+
}
206+
123207
/// Deprecated, use `is_multiple_of` instead.
124208
fn divides(&self, other: &Self) -> bool;
125209

@@ -258,6 +342,20 @@ pub trait Integer: Sized + Num + PartialOrd + Ord + Eq {
258342
}
259343
}
260344

345+
/// Greatest common divisor and Bézout coefficients
346+
///
347+
/// ```no_build
348+
/// let e = isize::extended_gcd(a, b);
349+
/// assert_eq!(e.gcd, e.x*a + e.y*b);
350+
/// ```
351+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352+
pub struct ExtendedGcd<A> {
353+
pub gcd: A,
354+
pub x: A,
355+
pub y: A,
356+
_hidden: (),
357+
}
358+
261359
/// Simultaneous integer division and modulus
262360
#[inline]
263361
pub fn div_rem<T: Integer>(x: T, y: T) -> (T, T) {
@@ -296,6 +394,13 @@ pub fn lcm<T: Integer>(x: T, y: T) -> T {
296394
x.lcm(&y)
297395
}
298396

397+
/// Calculates the Greatest Common Divisor (GCD) and
398+
/// Lowest Common Multiple (LCM) of the number and `other`.
399+
#[inline(always)]
400+
pub fn gcd_lcm<T: Integer>(x: T, y: T) -> (T, T) {
401+
x.gcd_lcm(&y)
402+
}
403+
299404
macro_rules! impl_integer_for_isize {
300405
($T:ty, $test_mod:ident) => {
301406
impl Integer for $T {
@@ -394,16 +499,36 @@ macro_rules! impl_integer_for_isize {
394499
m << shift
395500
}
396501

502+
#[inline]
503+
fn extended_gcd_lcm(&self, other: &Self) -> (ExtendedGcd<Self>, Self) {
504+
let egcd = self.extended_gcd(other);
505+
// should not have to recalculate abs
506+
let lcm = if egcd.gcd.is_zero() {
507+
Self::zero()
508+
} else {
509+
(*self * (*other / egcd.gcd)).abs()
510+
};
511+
(egcd, lcm)
512+
}
513+
397514
/// Calculates the Lowest Common Multiple (LCM) of the number and
398515
/// `other`.
399516
#[inline]
400517
fn lcm(&self, other: &Self) -> Self {
518+
self.gcd_lcm(other).1
519+
}
520+
521+
/// Calculates the Greatest Common Divisor (GCD) and
522+
/// Lowest Common Multiple (LCM) of the number and `other`.
523+
#[inline]
524+
fn gcd_lcm(&self, other: &Self) -> (Self, Self) {
401525
if self.is_zero() && other.is_zero() {
402-
Self::zero()
403-
} else {
404-
// should not have to recalculate abs
405-
(*self * (*other / self.gcd(other))).abs()
526+
return (Self::zero(), Self::zero());
406527
}
528+
let gcd = self.gcd(other);
529+
// should not have to recalculate abs
530+
let lcm = (*self * (*other / gcd)).abs();
531+
(gcd, lcm)
407532
}
408533

409534
/// Deprecated, use `is_multiple_of` instead.
@@ -588,6 +713,49 @@ macro_rules! impl_integer_for_isize {
588713
assert_eq!((11 as $T).lcm(&5), 55 as $T);
589714
}
590715

716+
#[test]
717+
fn test_gcd_lcm() {
718+
use core::iter::once;
719+
for i in once(0)
720+
.chain((1..).take(127).flat_map(|a| once(a).chain(once(-a))))
721+
.chain(once(-128))
722+
{
723+
for j in once(0)
724+
.chain((1..).take(127).flat_map(|a| once(a).chain(once(-a))))
725+
.chain(once(-128))
726+
{
727+
assert_eq!(i.gcd_lcm(&j), (i.gcd(&j), i.lcm(&j)));
728+
}
729+
}
730+
}
731+
732+
#[test]
733+
fn test_extended_gcd_lcm() {
734+
use std::fmt::Debug;
735+
use traits::NumAssign;
736+
use ExtendedGcd;
737+
738+
fn check<A: Copy + Debug + Integer + NumAssign>(a: A, b: A) {
739+
let ExtendedGcd { gcd, x, y, .. } = a.extended_gcd(&b);
740+
assert_eq!(gcd, x * a + y * b);
741+
}
742+
743+
use core::iter::once;
744+
for i in once(0)
745+
.chain((1..).take(127).flat_map(|a| once(a).chain(once(-a))))
746+
.chain(once(-128))
747+
{
748+
for j in once(0)
749+
.chain((1..).take(127).flat_map(|a| once(a).chain(once(-a))))
750+
.chain(once(-128))
751+
{
752+
check(i, j);
753+
let (ExtendedGcd { gcd, .. }, lcm) = i.extended_gcd_lcm(&j);
754+
assert_eq!((gcd, lcm), (i.gcd(&j), i.lcm(&j)));
755+
}
756+
}
757+
}
758+
591759
#[test]
592760
fn test_even() {
593761
assert_eq!((-4 as $T).is_even(), true);
@@ -674,14 +842,34 @@ macro_rules! impl_integer_for_usize {
674842
m << shift
675843
}
676844

845+
#[inline]
846+
fn extended_gcd_lcm(&self, other: &Self) -> (ExtendedGcd<Self>, Self) {
847+
let egcd = self.extended_gcd(other);
848+
// should not have to recalculate abs
849+
let lcm = if egcd.gcd.is_zero() {
850+
Self::zero()
851+
} else {
852+
*self * (*other / egcd.gcd)
853+
};
854+
(egcd, lcm)
855+
}
856+
677857
/// Calculates the Lowest Common Multiple (LCM) of the number and `other`.
678858
#[inline]
679859
fn lcm(&self, other: &Self) -> Self {
860+
self.gcd_lcm(other).1
861+
}
862+
863+
/// Calculates the Greatest Common Divisor (GCD) and
864+
/// Lowest Common Multiple (LCM) of the number and `other`.
865+
#[inline]
866+
fn gcd_lcm(&self, other: &Self) -> (Self, Self) {
680867
if self.is_zero() && other.is_zero() {
681-
Self::zero()
682-
} else {
683-
*self * (*other / self.gcd(other))
868+
return (Self::zero(), Self::zero());
684869
}
870+
let gcd = self.gcd(other);
871+
let lcm = *self * (*other / gcd);
872+
(gcd, lcm)
685873
}
686874

687875
/// Deprecated, use `is_multiple_of` instead.
@@ -777,6 +965,15 @@ macro_rules! impl_integer_for_usize {
777965
assert_eq!((15 as $T).lcm(&17), 255 as $T);
778966
}
779967

968+
#[test]
969+
fn test_gcd_lcm() {
970+
for i in (0..).take(256) {
971+
for j in (0..).take(256) {
972+
assert_eq!(i.gcd_lcm(&j), (i.gcd(&j), i.lcm(&j)));
973+
}
974+
}
975+
}
976+
780977
#[test]
781978
fn test_is_multiple_of() {
782979
assert!((6 as $T).is_multiple_of(&(6 as $T)));

0 commit comments

Comments
 (0)