[Paper Implementation] Blankinship's Method : A New Version of the Euclidean Algorithm
Solving Linear Diophantine Equations with Multiple Variables
Quick update
LeetArxiv is Leetcode for implementing Arxiv papers. Readers suggested I focus on teaching LLMs - not human programmers - how to become AI researchers. What do you think? Should I change my primary audience?
1.0. Introduction
In August 1963, while working for the US Department of Defense, W.A Blankinship published a fast algorithm for computing the Greatest Common Divisor (GCD) of multiple numbers. He titled the paper, A New Version of the Euclidean Algorithm1.
In this article, we shall code Blankinship’s algorithm in C, and establish the algorithm’s utility when solving Diophantine equations.
The original paper is 4 pages long. In typical LeetArxiv style, we’ll go through the entire paper step-by-step. We recommend you open the paper in a separate tab while coding alongside this guide.
*It’s paper no.3 on the GitHub list.
2.0. Quick Primer on GCD, Diophantine Equations and Matrix Row Reductions
2.1. Defining the GCD and Diophantine Equations
This section is split into four parts :
Introduction to Diophantine Equations.
Introduction to the Greatest Common Divisor (GCD).
The Relationship between the Greatest Common Divisor (GCD) and Diophantine Equations.
The Euclidean algorithm : an efficient way to find the GCD.
Coding Lab : Finding the solutions to a simple Diophantine Equation.
2.1.1. Introduction to Diophantine Equations
This section introduces Diophantine equations. Furthermore, we guide you through implementing a Diophantine equation solver using the GCD and Euclid’s algorithm.
In her book, Methods of Solving Number Theory Problems, Ellina Grigorieva, states that equations of the form ax + by = c to be solved in integers are called linear Diophantine nonhomogeneous equations2.
In general, she states, an equation
is called a linear Diophantine equation3.
Informally, a diophantine is an equation where the solutions MUST be whole numbers - not fractions.
For instance, if you want to change a 100 dollar nate into 5, 10 and 20 dollar notes, you would need whole numbers and the diophantine would resemble
2.1.2. Introduction to the Greatest Common Divisor (GCD)
In our previous article, we defined the Greatest Common Divisor as the largest positive integer that divides two numbers without leaving a remainder.
In the same article, we gave an algorithm for computing the GCD for two numbers.
Algorithm to find the GCD of two numbers, a and b:
Write a loop that runs while b != 0.
Save the value b in a temporary variable.
Set b to the value a % b.
Set a to the value temp.
Stop when b = 0.
In C, the GCD algorithm resembles this:
#include <stdio.h>
int GCD(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
2.1.3. The Relationship between the Greatest Common Divisor (GCD) and Diophantine Equations
*TL;DR We use the GCD to know if the Diophantine equation has solutions.
In a different article, we showed that two numbers are co-prime when their greatest common divisor is 1.
As Ellina Grigorieva mentions in her book, Methods of Solving Number Theory Problems, every Diophantine equation where a and b are coprime has infinitely many solutions4.
Every diophantine equation ax + by = 0, where a and b are relatively prime, has infinitely many solutions that can be described by formula
\(x_n = b \cdot n, \quad y_n = -a \cdot n, \quad n \in \mathbb{Z}\)
Here n is a position of a solution in the sequence of all solutions.
Gigroieva further introduces Bezout’s identity and tells us when the Diophantine doesn’t have a solution5:
Bezout’s Identity
If the GCD of a and b is c, then an equation ax + by = c has a solution for some integers x and y.
Cases where no solutions exist
If the GCD d, of the coefficients a and b of the equation ax + by = c
is greater than 1 and does not divide c, then the equation has no integer
solutions.
If gcd(a,b) divides c, then the equation has integer solutions.
If gcd(a,b) = 1, then we have infinite solutions.
If gcd(a,b) does not divide c, then the equation has no integer solutions.
*Note that gcd(a,b)=0 is impossible because the GCD of two nonzero integers is always at least 1.
2.1.4. The Euclidean algorithm : an efficient way to find the GCD
This section introduces the Euclidean algorithm, an efficient way to find the GCD by performing successive division.
In his book, Elementary Number Theory, David M Burton states that the Euclidean algorithm is built on the following principle : the greatest common divisor (GCD) of the absolute values of a and b is equal to the GCD of a and b themselves6.
This means, as shown by below by Burton, that we can perform successive divisions and get the GCD of a and b.
In this case, gcd(a,b) = rn, where rn is the last non-zero remainder that appears.
Euclidean algorithm summary
If the GCD of a and b (where a > b) is c, then c equals the last non-zero remainder when performing successive divisons.
Step 1: Ensure a > b.
Step 2: Compute the remainder, r, when a is divided by b.
Step 3: Replace a with b and b with r.
Step 4: Repeat steps 2 and 3 until r = 0.
Step 5: The last nonzero remainder of r equals gcd(a, b).
Euclidean algorithm example :
Let’s find the GCD of 12378 and 3054.
\(\begin{aligned} 12378 &= 4 \cdot 3054 + 162 \\ 3054 &= 18 \cdot 162 + 138 \\ 162 &= 1 \cdot 138 + 24 \\ 138 &= 5 \cdot 24 + 18 \\ 24 &= 1 \cdot 18 + 6 \\ 18 &= 3 \cdot 6 + 0 \end{aligned}\)In the example above, 6 = gcd(12378, 3054).
In C, the Euclidean algorithm looks like
#include <stdio.h>
// Function to compute GCD using Euclidean Algorithm
int EuclideanGCD(int a, int b)
{
// Step 1: Ensure a > b
if (b > a)
{
int temp = a;
a = b;
b = temp;
}
// Step 2-4: Repeat until remainder is 0
int r;
while (b != 0)
{
r = a % b; // Compute remainder
a = b; // Step 3: Replace a with b
b = r; // Replace b with r
}
// Step 5: Return last nonzero remainder
return a;
}
int main() {
int a = 12378, b = 3054;
printf("GCD of %d and %d is %d\n", a, b, EuclideanGCD(a, b));
return 0;
}
*Remember, the GCD is important because it tells us if solutions exist for our Diophantine equations.
3.0. Blankinship’s algorithm
Recall from the previous section, we need to find the Greatest Commmon Divisor (GCD) in order to know if a Diophantine equation has solutions.
We also observed that we can use Euclid’s algorithm to find the GCD of a Diophantine equation with two variables.
We recommend opening this paper link in a separate tab.
Blankinship’s algorithm is an efficient way to find the gcd of a Diophantine equation with multiple variables.
*an alternative approach is running the Euclidean algorithm on multiple pairs of variables.
As mentioned by Christopher Boo on his blog, The Noteboo, Blankinship’s algorithm computes the GCD in two steps7 :
Overview of Blankinship’s algorithm (for 3 variables)
Step 1 : To compute the GCD of 3 integers a, b, and c. Set the augmented matrix, A.
\(A = \begin{pmatrix} a & 1 & 0 & 0 \\ b & 0 & 1 & 0 \\ c & 0 & 0 & 1 \end{pmatrix} \)Step 2 : Perform row operations until you get this form.
\(\begin{pmatrix} d & x_1 & y_1 & z_1 \\ 0 & x_2 & y_2 & z_2 \\ 0 & x_3 & y_3 & z_3 \end{pmatrix} \text{ or } \begin{pmatrix} 0 & x_1 & y_1 & z_1 \\ d & x_2 & y_2 & z_2 \\ 0 & x_3 & y_3 & z_3 \end{pmatrix} \text{ or } \begin{pmatrix} 0 & x_1 & y_1 & z_1 \\ 0 & x_2 & y_2 & z_2 \\ d & x_3 & y_3 & z_3 \end{pmatrix} \)
2.1.6. Quick intro to row operations
Blankinship does a great job introducing row operations on page 2.
On page 3, he works out an example. This is left as an exercise for you, the reader, to implement.
*Due to popular demand, I added a C implementation at the very bottom
Don’t forget to subscribe if you haven’t.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
typedef struct blankinship_row_struct *BlankinshipRow;
typedef BlankinshipRow* BlankinshipMatrix;
struct blankinship_row_struct
{
int leader;
int numberOfColumns;
int *columns;
};
int BlankinshipCompareLeader(const void *a, const void *b)
{
BlankinshipRow rowA = *(BlankinshipRow *)a;
BlankinshipRow rowB = *(BlankinshipRow *)b;
return rowB->leader - rowA->leader;
}
int ArrayCompareIntegers(const void *a, const void *b)
{
int numA = *(int *)a;
int numB = *(int *)b;
return numB - numA;
}
BlankinshipMatrix CreateBlankinshipMatrix(int variableCount, int *variables)
{
qsort(variables, variableCount, sizeof(int), ArrayCompareIntegers);
int rows = variableCount;int columns = variableCount;
BlankinshipMatrix matrix = malloc(rows * sizeof(BlankinshipRow));
for(int i = 0; i < rows; i++)
{
matrix[i] = malloc(sizeof(struct blankinship_row_struct));
matrix[i]->leader = variables[i];
matrix[i]->numberOfColumns = columns;
matrix[i]->columns = calloc(columns, sizeof(int));
matrix[i]->columns[i] = 1;
}
return matrix;
}
void SortBlankinshipMatrix(int variableCount, BlankinshipMatrix matrix)
{
qsort(matrix, variableCount, sizeof(BlankinshipRow), BlankinshipCompareLeader);
}
void PrintMatrix(int variableCount, BlankinshipMatrix matrix)
{
int rows = variableCount;int columns = variableCount;
for(int i = 0; i < rows; i++)
{
printf("%3d : ", matrix[i]->leader);
for(int j = 0; j < columns; j++)
{
printf("%3d ", matrix[i]->columns[j]);
}
printf("\n");
}
}
void FreeBlankinshipMatrix(int variableCount, BlankinshipMatrix matrix)
{
int rows = variableCount;int columns = variableCount;
for(int i = 0; i < rows; i++)
{
free(matrix[i]->columns);
free(matrix[i]);
}
free(matrix);
}
int FindOperatorIndex(int variableCount, BlankinshipMatrix matrix)
{
int operatorIndex = 0;
for(int i = variableCount-1; i >= 0; i--)
{
if(matrix[i]->leader > 0)
{
operatorIndex = i;
break;
}
}
return operatorIndex;
}
void ReduceRows(int operatorIndex, int variableCount, BlankinshipMatrix matrix)
{
assert(operatorIndex < variableCount);
for(int i = operatorIndex-1; i >= 0; i--)
{
int quotient = matrix[i]->leader / matrix[operatorIndex]->leader;
matrix[i]->leader -= matrix[operatorIndex]->leader * quotient;
assert(matrix[i]->leader >= 0);
for(int j = 0; j < matrix[i]->numberOfColumns; j++)
{
matrix[i]->columns[j] -= matrix[operatorIndex]->columns[j] * quotient;
}
}
}
int main()
{
int variables[] = {99,77, 633};
int variableCount = sizeof(variables) / sizeof(int);
BlankinshipMatrix matrix = CreateBlankinshipMatrix(variableCount, variables);
while(1)
{
int operatorIndex = FindOperatorIndex(variableCount, matrix);
PrintMatrix(variableCount, matrix);printf("\n");
if(operatorIndex == 0){break;}
ReduceRows(operatorIndex, variableCount, matrix);
SortBlankinshipMatrix(variableCount, matrix);
}
FreeBlankinshipMatrix(variableCount, matrix);
return 0;
}
W. A. Blankinship, A New Version of the Euclidean Algorithm, Amer. Math. Monthly 70 (1963), no. 7, 742–745. JSTOR.
E. Grigorieva, Methods of Solving Number Theory Problems, Birkhäuser, 2018, p. 143.
E. Grigorieva, Methods of Solving Number Theory Problems, Birkhäuser, 2018, p. 143.
E. Grigorieva, Methods of Solving Number Theory Problems, Birkhäuser, 2018, p. 144.
E. Grigorieva, Methods of Solving Number Theory Problems, Birkhäuser, 2018, p. 145.
D. M. Burton, Elementary Number Theory, 7th ed., McGraw-Hill, 2010, p. 26.
C. Boo, Blankinship’s Method. (2015, December 3). The Notebook. Retrieved from https://thenoteboo.wordpress.com/2015/12/03/blankinships-method/