Mastering Modular Arithmetic for Algorithms
Modular Inverse
The problem with division
You've seen how addition, subtraction, and multiplication work smoothly in modular arithmetic. We can take the modulus at each step to keep numbers manageable. Division, however, is a different story. The straightforward approach doesn't work.
For example, is , which is . But if we try to take the modulus first, we get , which is . This case seems to work.
Let's try another: is , which is . But is . So far so good.
What about ? That's , which is . But is . Still seems to work.
Okay, let's try this one: . That's , which is . But is . Now we have a problem. What is in the world of integers and remainders?
In regular arithmetic, dividing by a number is the same as multiplying by its reciprocal. For instance, dividing by 5 is the same as multiplying by , or . We need an equivalent concept for modular arithmetic.
Modular multiplicative inverse
The modular equivalent of a reciprocal is called the modular multiplicative inverse. The inverse of a number modulo is a number such that their product is congruent to 1.
With this inverse, we can redefine division. To compute , we instead calculate .
This raises a critical question: does an inverse always exist?
In short, no. A modular inverse exists only if and are coprime. That is, their greatest common divisor must be 1.
It can be proven that the modular inverse exists if and only if and are relatively prime (i.e. ).
For example, the inverse of exists because . We are looking for a number where . If we test values, we find that works, because , and . So, .
Conversely, the inverse of does not exist because . No matter which integer you multiply by 2, the result modulo 4 will always be an even number ( or ), never .
Finding the inverse by trial and error is slow. We need a more systematic and efficient method, especially for large numbers.
Using the Extended Euclidean Algorithm
The Extended Euclidean Algorithm is a powerful tool that not only finds the greatest common divisor of two integers and , but also finds two other integers, and , that satisfy Bézout's identity.
How does this help us find the modular inverse? If we know that and are coprime, then their gcd is 1. The equation becomes:
Now, let's take this equation modulo . The term becomes , because any multiple of is congruent to . This simplifies the equation dramatically.
The algorithm works recursively. The base case for the recursion is when , where . In this case, and . For the recursive step, we use the results from a smaller problem, extendedGcd(m % a, a), to build the solution for extendedGcd(a, m).
Here is a C++ implementation. It returns the gcd and finds the values for and , which are passed by reference.
// C++ function for Extended Euclidean Algorithm
// It returns gcd(a, b) and finds the values of x and y such that
// ax + by = gcd(a, b)
int extendedGcd(int a, int b, int &x, int &y) {
// Base Case
if (a == 0) {
x = 0;
y = 1;
return b;
}
int x1, y1; // To store results of recursive call
int gcd = extendedGcd(b % a, a, x1, y1);
// Update x and y using results of recursive call
x = y1 - (b / a) * x1;
y = x1;
return gcd;
}
Putting it all together
Now we can write a function to calculate the modular inverse. This function will use our extendedGcd implementation. Remember, we must first check that an inverse actually exists.
#include <iostream>
// Extended Euclidean Algorithm function from before
int extendedGcd(int a, int b, int &x, int &y) { /* ... */ }
// Function to find modular inverse of a under modulo m
void modInverse(int a, int m) {
int x, y;
int g = extendedGcd(a, m, x, y);
if (g != 1) {
std::cout << "Inverse doesn't exist";
} else {
// The value of x can be negative, so we adjust it
int result = (x % m + m) % m;
std::cout << "Modular multiplicative inverse is " << result;
}
}
int main() {
int a = 3, m = 7;
modInverse(a, m); // Output: Modular multiplicative inverse is 5
return 0;
}
Notice the line (x % m + m) % m. The Extended Euclidean Algorithm can return a negative value for . Since we want a positive result in the range , we add to bring it into the positive range before taking the modulus one last time. This is a standard trick for handling potential negative results in modular arithmetic.
With this function, we can now perform modular division. To calculate , you would first call modInverse(a, m) to get , and then compute using the modular multiplication techniques you've already learned. This is a fundamental operation in many algorithms used in competitive programming, particularly in combinatorics and number theory problems.
Why is direct division, such as calculating , problematic in modular arithmetic?
The modular multiplicative inverse of a number modulo is a number such that: