Extended GCD
Given two integers, and , find their greatest common divisor and, and such coefficients and , that
By Bézout’s identity such a representation exists.
The algorithm easily expands to any number of variables . First, we find the GCD and coefficients and of and , then we compute the GCD and coefficients and of and and update and , and so on always updating after computing for .
Algorithm
Let’s recall a property of GCD: . This gives us that . So, starting with coefficients we can go backwards up the recursive calls.
So, on forward pass from
assuming , we have
This gives us
And the original coefficients are
Implementation
Well, recursive algorithm requires recursion. The standard approach in this case is passing and the way you like, and local creation of and and passing them by reference.
// code from AfCP
int gcdx(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int x1, y1;
int d = gcd(b, a % b, x1, y1);
x = y1;
y = x1 - y1 * (a / b);
return d;
}Personally, I prefer the approach of using a recursive lambda function inside the function itself. To a lambda recursively we need either specify its type with std::function:
// `std::function` version
std::pair<int, std::pair<int, int>> gcdx(int a, int b) {
if (b == 0) {
return {a, {1, 0}};
}
std::function<int(int, int, int&, int&)> gcdx_recursive = [&gcdx_recursive](int a, int b, int& x, int& y) -> int {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int x1, y1;
int d = gcdx_recursive(b, a % b, x1, y1);
x = y1;
y = x1 - y1 * (a / b);
return d;
};
int x, y;
int d = gcdx_recursive(a, b, x, y);
return {d, {x, y}};
}or pass this lambda in itself as a parameter:
std::pair<int, std::pair<int, int>> gcdx(int a, int b) {
if (b == 0) {
return {a, {1, 0}};
}
auto gcdx_recursive = [](auto self, int a, int b, int& x, int& y) -> int {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int x1, y1;
int d = self(self, b, a % b, x1, y1);
x = y1;
y = x1 - y1 * (a / b);
return d;
};
int x, y;
int d = gcdx_recursive(gcdx_recursive, a, b, x, y);
return {d, {x, y}};
}Iterative algorithm
Well, let’s remember the property
. Let’s denote (the index will play its role later):
with ,
with , and so on.
The chain stops when , and the last non-zero remainder, , is the GCD. Start of the algorithm is a bit…, it does not start with . From the substitutions we see, that and .
can be rewritten as
where for . Each such equation is Euclidean division, or division with remainder.
Now we need to do something with the coefficients and . We know, that and satisfy the equation
So let’s define , where and are the coefficients of and in the -th step of the algorithm. With this representation and .
Back to the division chain:
Now we see the relationship between and :
Where and sequences start with
The algorithm is to just compute sequences , and using the recurrence relations and initial conditions above, until . But wait,
where the sequence is needed to compute and , which are independent. So one of the sequences or can be dropped and computed from the other when the algorithm ends:
std::pair<int, std::pair<int, int>> gcdx_it(int a, int b) {
auto r0 = a, r1 = b;
auto s0 = 1, s1 = 0;
while (r1 != 0) {
auto q = r0 / r1;
std::swap(r1, r0 -= q * r1);
std::swap(s1, s0 -= q * s1);
}
return {r0, {s0, (r0 - a * s0) / b}};
}Templated implementation
What are the types? The analysis is pretty similar to those of the GCD function, but in extended version we also need to analyze the types of the coefficients and .
Like the GCD function, the extended Euclidean algorithm is defined for unsigned integers, but it can be generalized to any integral type of arguments. Basically, we have two positive integers and , their GCD is definitely smaller than and (or equal to or ), so one of the coefficients or is always negative. In signed case, the signs of and are simply transferred to the coefficients and . So, the type of the GCD is the common type of and , and the type of the coefficients and is its signed version.
Note
The version of the function that takes only unsigned integers is needed due to the fact that cannot be computed afterward properly, so it should be computed through the sequence.
Warning
abl::gcdx of two arguments returns the coefficients as a std::tuple, while abl::gcdx of three arguments or of std::initializer_list returns the coefficients as a std::vector. It will be changed in the future.
// std::integral, std::is_signed, std::is_unsigned
#include <concepts>
// std::numeric_limits
#include <limits>
// std::common_type, std::make_signed, std::make_unsigned,
// std::is_convertible (std::is_nothrow_convertible)
#include <type_traits>
// std::invalid_argument
#include <stdexcept>
// std::swap
#include <utility>
#include <tuple>
#include <vector>
namespace abl {
template <typename T, typename U>
requires requires { typename std::common_type_t<T, U>; } &&
(std::is_convertible_v<T, std::common_type_t<T, U>> ||
std::is_convertible_v<U, std::common_type_t<T, U>>) &&
std::integral<std::common_type_t<T, U>> &&
std::is_signed_v<std::common_type_t<T, U>>
constexpr std::pair<
std::common_type_t<T, U>,
std::tuple<std::common_type_t<T, U>, std::common_type_t<T, U>>>
gcdx(const T& a, const U& b) noexcept(
std::is_nothrow_convertible_v<T, std::common_type_t<T, U>> ||
std::is_nothrow_convertible_v<U, std::common_type_t<T, U>>) {
using common_t = std::common_type_t<T, U>;
using common_ut = std::make_unsigned_t<common_t>;
common_ut r0 = a < 0 ? (a == std::numeric_limits<common_t>::min()
? static_cast<common_ut>(a)
: static_cast<common_ut>(-a))
: static_cast<common_ut>(a);
common_ut r1 = b < 0 ? (b == std::numeric_limits<common_t>::min()
? static_cast<common_ut>(b)
: static_cast<common_ut>(-b))
: static_cast<common_ut>(b);
if (r0 == 0) return {static_cast<common_t>(r1), {0, (b < 0 ? -1 : 1)}};
if (r1 == 0) return {static_cast<common_t>(r0), {(a < 0 ? -1 : 1), 0}};
common_t s0 = 1, s1 = 0;
while (r1 != 0) {
common_ut q = r0 / r1;
std::swap(r1, r0 -= q * r1);
std::swap(s1, s0 -= static_cast<common_t>(q) * s1);
}
return {static_cast<common_t>(r0),
{s0 * (a < 0 ? -1 : 1),
(static_cast<common_t>(r0) - static_cast<common_t>(a) * s0) /
static_cast<common_t>(b) * (b < 0 ? -1 : 1)}};
}
template <typename T, typename U>
requires requires { typename std::common_type_t<T, U>; } &&
(std::is_convertible_v<T, std::common_type_t<T, U>> ||
std::is_convertible_v<U, std::common_type_t<T, U>>) &&
std::integral<std::common_type_t<T, U>> &&
std::is_unsigned_v<std::common_type_t<T, U>>
constexpr std::pair<std::common_type_t<T, U>,
std::tuple<std::make_signed_t<std::common_type_t<T, U>>,
std::make_signed_t<std::common_type_t<T, U>>>>
gcdx(const T& a, const U& b) noexcept(
std::is_nothrow_convertible_v<T, std::common_type_t<T, U>> ||
std::is_nothrow_convertible_v<U, std::common_type_t<T, U>>) {
using common_ut = std::common_type_t<T, U>;
using common_t = std::make_signed_t<common_ut>;
auto r0 = static_cast<common_ut>(a), r1 = static_cast<common_ut>(b);
if (r0 == 0) return {r1, {0, 1}};
if (r1 == 0) return {r0, {1, 0}};
common_t s0 = 1, s1 = 0;
common_t t0 = 0, t1 = 1;
while (r1 != 0) {
common_ut q = r0 / r1;
std::swap(r1, r0 -= q * r1);
std::swap(s1, s0 -= static_cast<common_t>(q) * s1);
std::swap(t1, t0 -= static_cast<common_t>(q) * t1);
}
return {r0, {s0, t0}};
}
template <typename T>
requires std::integral<T> && std::is_signed_v<T>
constexpr std::pair<T, std::vector<T>> gcdx(std::initializer_list<T> numbers) {
if (numbers.size() < 2)
throw std::invalid_argument(
"abl::gcdx takes at least two integer argumnets");
std::vector<T> coeffs_vec = numbers;
T d;
std::tuple<T, T> coeffs;
std::tie(d, coeffs) = gcdx(coeffs_vec[0], coeffs_vec[1]);
coeffs_vec[0] = std::get<0>(coeffs);
coeffs_vec[1] = std::get<1>(coeffs);
for (std::size_t i = 2; i < numbers.size(); i++) {
std::tie(d, coeffs) = gcdx(d, coeffs_vec[i]);
for (std::size_t j = 0; j < i; coeffs_vec[j++] *= std::get<0>(coeffs));
coeffs_vec[i] = std::get<1>(coeffs);
}
return {d, coeffs_vec};
}
template <typename... Ts>
requires(sizeof...(Ts) > 2) &&
(std::is_convertible_v<Ts, std::common_type_t<Ts...>> && ...) &&
(std::signed_integral<Ts> && ...)
constexpr std::pair<std::common_type_t<Ts...>,
std::vector<std::common_type_t<Ts...>>>
gcdx(Ts&&... numbers) noexcept(
(std::is_nothrow_convertible_v<Ts, std::common_type_t<Ts...>> && ...)) {
return gcdx({std::forward<Ts>(numbers)...});
}
} // namespace abl