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.