Modular Multiplicative Inverse In C++ - C++ Programming Tutorial
C++ Course / Number Theory / Modular Multiplicative Inverse In C++

Modular Multiplicative Inverse In C++

BLUF: Mastering Modular Multiplicative Inverse In C++ is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: Modular Multiplicative Inverse In C++

C++ is renowned for its efficiency. Learn how Modular Multiplicative Inverse In C++ enables low-level control and high-performance computing in the tutorial below.

Introduction

In various areas of computer science and mathematics, modular arithmetic plays a crucial role. A fundamental concept within modular arithmetic is the modular multiplicative inverse. This guide will delve into the definition of modular multiplicative inverse, its significance, and the efficient method of calculating it using C++.

Problem Statement:

We aim to discover an integer x that satisfies the equation (a * x) % m = 1, given two integers a and m. Essentially, we are seeking a value for x that results in a remainder of 1 when the product of a and x is divided by m.

  • Exploring Modular Arithmetic Concepts

Just prior to delving into the concept of modular multiplicative inverse, it is beneficial to have a quick review of modular arithmetic. Modular arithmetic involves working with remainders. For instance, in modulo 5 arithmetic, 7 mod 5 equals 2 as the remainder is 2 when 7 is divided by 5.

  • Significance of Modular Multiplicative Inverse

Cryptography heavily depends on the presence of the modular multiplicative inverse, a concept extensively utilized in number theory for various operations, especially in algorithms involving modulo calculations. For example, in RSA encryption, the decryption keys are generated through the computation of modular multiplicative inverses.

  • Performing Calculations for Modular Multiplicative Inverse

There exist numerous techniques for calculating modular multiplicative inverse, including the Extended Euclidean Algorithm and Fermat's Little Theorem. Nonetheless, our emphasis is on employing the Extended Euclidean Algorithm within C++.

Features of Modular Multiplicative Inverse:

There are several features of the Modular Multiplicative Inverse in C++. Some main features of this function are as follows:

  • It calculates the modular multiplicative inverse of a number a modulo m and is mainly used in number theory, cryptography, computing, and other mathematical applications.
  • Extended Euclidean Algorithm: This algorithm uses the Extended Euclidean Algorithm to find the greatest common divisor (GCD) of two integers a and m, as well as x and y, which satisfy ax + my = gcd .
  • Efficient Time Complexity: The algorithm's time complexity is logarithmic in terms of a and m values provided, which makes it efficient for large size numbers. By recursively splitting input values, the algorithm requires minimal operations thereby achieving this simplicity.
  • Space Efficiency: The space complexity of this algorithm meets its time complexity by primarily requiring spaces for storing inputs, variables of recursion, and temporary variables along with coefficients. Thus, this space usage is efficient and does not increase directly with increases in the size of inputs.
  • Error Handling: This program's error handling part checks if a modular multiplicative inverse exists. If the GCD between 'a' and 'm' yields 1 (meaning they are not coprime), there is no modular inverse. Hence, an appropriate error indicator should be outputted by such an algorithm.
  • Usability and Extensibility: The algorithm is designed as a C++ function, which makes it feasible to reuse and incorporate in bigger programs or projects that require modular arithmetic operations. It will be working in a modular and structured manner so as to improve the readability and maintainability.
  • Demonstration in Main Function: The code has a main function that shows how the modular multiplicative inverse function can be used with some example inputs. It provides an example of how the algorithm is used, what it does, and what its output looks like.
  • History:

  • Ancient Roots: Ancient Babylonians and Egyptians (3000 BCE - 500 BCE) : These civilizations touched on elements of early mathematics such as arithmetic. Although they did not specifically deal with modular arithmetic or have concepts of multiplicative inverses as we know them today, their contributions laid the foundation for early developments in mathematics.
  • Greek Mathematicians (circa 600 BCE - 300 BCE): There were mathematicians like Euclid and Diophantus who made significant contributions to number theory especially concerning modular arithmetic. Euclid's Algorithm for finding greatest common divisor (GCD) is closely related to Extended Euclidean Algorithm EEA employed while computing modular multiplicative inverses.
  • Fermat and Euler (17th - 18th Century): Number theory was also developed by Pierre de Fermat, Leonhard Euler , and others. Understanding of modular arithmetic depends on Fermat's Little Theorem and Euler' s Totient Function.
  • 20th Century to Present: Cryptography has emphasized the significance of modular arithmetic and inverses, which were invented together with computer science. RSA encryption is one example where algorithms for calculating modular multiplicative inverses found practical applications in encryption/decryption techniques.
  • Development of Efficient Algorithms: Mathematicians and computer scientists have optimized mathematical techniques like the Extended Euclidean Algorithm as well as other variations for computational efficiency over time while developing algorithms used in calculating Modular Multiplicative Inverse.
  • Cryptography, Computer Science, and Mathematics: Today, modular arithmetic and its inverses play a vital role in various fields, including cryptography, computer science, number theory, and algorithm design. They form the basis for secure communication protocols, cryptographic systems, and mathematical algorithms.
  • Program 1:

Let's consider an example to demonstrate the concept of modular multiplicative inverse in the C++ programming language.

Example

#include <iostream>

using namespace std;

int extendedEuclidean(int a, int b, int& x, int& y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int x1, y1;
    int gcd = extendedEuclidean(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return gcd;
}

int modularMultiplicativeInverse(int a, int m) {
    int x, y;
    int gcd = extendedEuclidean(a, m, x, y);
    if (gcd != 1) {
        // Modular inverse doesn't exist
        return -1;
    } else {
        // Ensure x is positive
        return (x % m + m) % m;
    }
}

int main() {
    int a = 7;
    int m = 11;
    int inverse = modularMultiplicativeInverse(a, m);
    if (inverse != -1) {
        cout << "The modular multiplicative inverse of " << a << " mod " << m << " is " << inverse << endl;
    } else {
        cout << "Modular multiplicative inverse doesn't exist." << endl;
    }
    return 0;
}

Output:

Output

The modular multiplicative inverse of 7 mod 11 is 8

Explanation:

  • Input Parameters: This example takes two integers a and m where a is the number whose inverse we want to find and m is modulus.
  • Use Extended Euclidean Algorithm: Next, use Extended Euclidean Algorithm to find GCD of a and m with coefficient x and y such that ax + my = gcd .
  • Check for Existence: If GCD of a and m ≠ 1, there does not exist any modular multiplicative inverse because they are not coprime (i.e., they have a common factor other than 1).
  • Calculate Inverse: On the contrary, if GCD equals 1 (a is coprime with m), x can be said to be the modular multiplicative inverse of a modulo m. However, it should be noted that x needs to be positive and within the range of m so that we apply modulo m to x but add, if necessary, another instance of it with an absolute value equal to m to make it positive.

Time and Space Complexities:

In a broad sense, the time complexity of the Extended Euclidean Algorithm for calculating a modular multiplicative inverse is efficient. It typically runs in O(log(min(a,m))) time, where 'a' and 'm' represent the input integers. The algorithm involves recursive calls to calculate Greatest Common Divisors (GCDs) and coefficients 'x' and 'y'. With each call, the algorithm reduces the numbers involved by half, resulting in a logarithmic time complexity. Nevertheless, the actual number of operations is directly influenced by the input values and the specific implementation nuances.

Space Utilization:

  • The algorithm's space complexity remains optimal, mainly necessitating storage for variables essential in computations, like input integers a and m, coefficients x and y, and temporary variables for recursion.
  • Given the algorithm's recursive nature, the space complexity hinges on the recursion stack's maximum depth. In the Extended Euclidean Algorithm, this depth approximately aligns with O(log(min(a,m))), akin to the time complexity.
  • Program 2:

Let's consider another instance to demonstrate the concept of modular multiplicative inverse in the C++ programming language.

Example

#include <iostream>
#include <vector>
#include <queue>
#include <limits>

using namespace std;

struct Edge {
    int to;
    int weight;
};

void dijkstra(vector<vector<Edge>>& graph, int source, vector<int>& distance) {
    int n = graph.size();
    distance.resize(n, numeric_limits<int>::max());
    distance[source] = 0;
    
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.push({0, source});

    while (!pq.empty()) {
        int u = pq.top().second;
        int dist_u = pq.top().first;
        pq.pop();

        if (dist_u > distance[u]) {
            continue;
        }

        for (const Edge& edge : graph[u]) {
            int v = edge.to;
            int weight_uv = edge.weight;

            if (distance[u] + weight_uv < distance[v]) {
                distance[v] = distance[u] + weight_uv;
                pq.push({distance[v], v});
            }
        }
    }
}

int main() {
    int n = 6; // Number of nodes
    vector<vector<Edge>> graph(n);

    // Example graph edges and weights
    graph[0].push_back({1, 5});
    graph[0].push_back({2, 3});
    graph[1].push_back({3, 6});
    graph[1].push_back({2, 2});
    graph[2].push_back({4, 4});
    graph[2].push_back({5, 2});
    graph[2].push_back({3, 7});
    graph[3].push_back({4, 1});
    graph[4].push_back({5, 2});

    vector<int> distance;
    dijkstra(graph, 0, distance);

    cout << "Shortest distances from source node 0:" << endl;
    for (int i = 0; i < n; ++i) {
        cout << "Node " << i << ": " << distance[i] << endl;
    }

    return 0;
}

Output:

Output

Shortest distances from source node 0:
Node 0: 0
Node 1: 5
Node 2: 3
Node 3: 10
Node 4: 7
Node 5: 5

Explanation:

  1. Data Structures Used:
  • Edge Struct: It represents an edge in the graph, containing the target node (to) and the weight of the edge (weight).
  • Priority Queue: It is used to prioritize nodes based on their distances from the source node.
  • Function: void dijkstra(vector<vector<Edge>>& graph, int source, vector<int>& distance)
  1. Input Parameters:
  • graph: The graph represented as an adjacency list where graph[u] contains all edges originating from node u.
  • source: It is the index of the source node from which distances are calculated.
  • distance: A vector to store the shortest distances from the source node to each node in the graph.
  1. Algorithm:
  • It initialize the distance vector with maximum values except for the source node (distance[source] = 0) .
  • Next, initialize a priority queue (pq) to store pairs of distances and node indices, initially containing the source node ({0, source}).
  • Repeat until pq is empty:
  • Pop the top element ({dist_u, u}) from the priority queue.
  • If the distance to u is greater than the stored distance, skip this iteration (optimization step).
  • Iterate through all edges (edge) from node u:
  • Calculate the new distance to the target node v (distance[u] + weight_uv) .
  • If this new distance is shorter than the current distance to v, update distance[v] and push {distance[v], v} to pq.
  • After the loop, distance contains the shortest distances from the source node to all other nodes in the graph.
  1. Main Function:
  • In the main function, we creates an example graph with nodes 0 to 5 and adds weighted edges between nodes.
  • After that, calls the dijkstra function to calculate the shortest distances from node 0 (the source node) to all other nodes.
  • Outputs the shortest distances for each node from the source node.

Time and Space Efficiencies:

  • Time Complexity: O((V+E)logV)
  • Space Complexity: O(V+E)
  • Conclusion:

In summary, having a grasp of modular multiplicative inverses is crucial in various mathematical and computational contexts, including cryptography and number theory. The C++ Extended Euclidean Algorithm offers an effective solution for calculating modular multiplicative inverses, enabling us to tackle a wide range of problems related to modular arithmetic.

Input Required

This code uses input(). Please provide values below:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience