Core Java

Modular arithmetic operations in Java

In competitive programming, handling large numbers efficiently is crucial. Often, problems require computing results under a certain modulo to avoid overflow and ensure numbers remain manageable. One of the most common moduli used is 10^9^ + 7 (denoted as MOD), which is a prime number and ensures properties favorable for modular arithmetic. Let us delve into understanding how Java modular arithmetic operations provide efficient solutions for handling large numbers in computational problems.

1. Modular Sum

The modular sum of two numbers a and b is calculated as:

(a + b) % MOD

In modular arithmetic, addition behaves as usual, except the result is reduced by MOD to avoid overflow. This ensures that the result stays within bounds. Let’s take a look at the following code example:

class ModuloArithmetic {
    static final int MOD = 1000000007;

    public static int modularSum(int a, int b) {
        return (a + b) % MOD;
    }

    public static void main(String[] args) {
        int a = 1000000006;
        int b = 5;
        int result = modularSum(a, b);
        System.out.println(result);
    }
}

The modularSum function adds two integers a and b and returns the result modulo MOD. In the given example, the result of 1000000006 + 5 under modulo 10^9 + 7 is 4. When we run the above code, the following output will be shown on the IDE console:

4

2. Modular Subtraction

Modular subtraction is calculated as:

(a - b + MOD) % MOD

Adding MOD ensures that the result is always non-negative, even if a is smaller than b. Let’s take a look at the following code example:

class ModuloArithmetic {
    static final int MOD = 1000000007;

    public static int modularSubtraction(int a, int b) {
        return (a - b + MOD) % MOD;
    }

    public static void main(String[] args) {
        int a = 10;
        int b = 15;
        int result = modularSubtraction(a, b);
        System.out.println(result);
    }
}

Here, we subtract b from a and add MOD to the result to prevent negative numbers. Then we apply the modulo operation to ensure the result is within the allowed range. When we run the above code, the following output will be shown on the IDE console:

1000000002

3. Modular Multiplication

In modular arithmetic, multiplication is performed as follows:

(a * b) % MOD

It is especially useful in cases where the result of a * b could overflow. Let’s take a look at the following code example:

class ModuloArithmetic {
    static final int MOD = 1000000007;

    public static long modularMultiplication(long a, long b) {
        return (a * b) % MOD;
    }

    public static void main(String[] args) {
        long a = 123456789;
        long b = 987654321;
        long result = modularMultiplication(a, b);
        System.out.println(result);
    }
}

We multiply a and b, and then take the result modulo MOD to prevent overflow. The function accepts and returns long values to handle large numbers. When we run the above code, the following output will be shown on the IDE console:

259106859

4. Modular Exponentiation

Modular exponentiation efficiently calculates a^b % MOD using a technique called “exponentiation by squaring,” which has a time complexity of O(log b). Let’s take a look at the following code example:

class ModuloArithmetic {
    static final int MOD = 1000000007;

    public static long modularExponentiation(long a, long b) {
        long result = 1;
        a = a % MOD;
        while (b > 0) {
            if ((b & 1) == 1) {
                result = (result * a) % MOD;
            }
            a = (a * a) % MOD;
            b >>= 1;
        }
        return result;
    }

    public static void main(String[] args) {
        long a = 2;
        long b = 10;
        long result = modularExponentiation(a, b);
        System.out.println(result);
    }
}

This function repeatedly squares the base a and multiplies it by the result when the exponent b is odd. This technique significantly reduces the number of multiplications, making it highly efficient for large exponents. When we run the above code, the following output will be shown on the IDE console:

1024

5. Modular Multiplicative Inverse

The modular multiplicative inverse of a under modulo MOD is a number x such that:

(a * x) % MOD = 1

For a prime modulus, the inverse can be computed using Fermat’s Little Theorem, which states:

a^(MOD - 2) % MOD

Let’s take a look at the following code example:

class ModuloArithmetic {
    static final int MOD = 1000000007;

    public static long modularInverse(long a) {
        return modularExponentiation(a, MOD - 2);
    }

    public static void main(String[] args) {
        long a = 3;
        long result = modularInverse(a);
        System.out.println(result);
    }
}

Using Fermat’s Little Theorem, we calculate the modular inverse of a by computing a^(MOD - 2) % MOD. The inverse of 3 modulo 10^9 + 7 is 333333336. When we run the above code, the following output will be shown on the IDE console:

333333336

6. Modular Division

The modular division is calculated as:

(a / b) % MOD = (a * modularInverse(b)) % MOD

This is because division under a modulus is not directly possible, so we convert it to multiplication by the inverse. Let’s take a look at the following code example:

class ModuloArithmetic {
    static final int MOD = 1000000007;

    public static long modularDivision(long a, long b) {
        long inverse_b = modularInverse(b);
        return (a * inverse_b) % MOD;
    }

    public static void main(String[] args) {
        long a = 10;
        long b = 2;
        long result = modularDivision(a, b);
        System.out.println(result);
    }
}

The division is performed by multiplying a by the modular inverse of b (since division under modulo isn’t directly feasible). This method ensures that the result is always within bounds. When we run the above code, the following output will be shown on the IDE console:

5

7. Conclusion

Modular arithmetic, particularly under the modulus 10^9 + 7, is a powerful and essential concept in computational problem-solving. Each operation in modular arithmetic ensures that large numbers are efficiently handled without overflow, keeping results within a manageable range. The modular sum operation simply adds two numbers and reduces the result by the modulus, while the modular subtraction adjusts for potential negative values by adding the modulus before applying it. These basic operations are extended by modular multiplication, which allows the safe computation of products without exceeding data limits. For exponentiation, the modular exponential leverages a logarithmic time complexity technique called “exponentiation by squaring,” which enables fast and efficient computation of large powers.

One of the more advanced techniques is the modular multiplicative inverse, where the division in modular arithmetic is made possible using Fermat’s Little Theorem. This method allows us to calculate the inverse of a number under the modulus, enabling safe division through modular division. Together, these operations form the backbone of many algorithms in competitive programming and cryptography, ensuring that we can work with large numbers while maintaining efficiency and correctness. Understanding and mastering these operations allows for solving complex problems while adhering to constraints such as time and memory limits, making them invaluable for high-performance computing.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button