Skip to content

feat: add fast exponentiation algorithm #5715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/main/java/com/thealgorithms/maths/FastExponentiation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.thealgorithms.maths;

/**
* This class provides a method to perform fast exponentiation (exponentiation by squaring),
* which calculates (base^exp) % mod efficiently.
*
* <p>The algorithm works by repeatedly squaring the base and reducing the exponent
* by half at each step. It exploits the fact that:
* <ul>
* <li>If exp is even, (base^exp) = (base^(exp/2))^2</li>
* <li>If exp is odd, (base^exp) = base * (base^(exp-1))</li>
* </ul>
* The result is computed modulo `mod` at each step to avoid overflow and keep the result within bounds.
* </p>
*
* <p><strong>Time complexity:</strong> O(log(exp)) — much faster than naive exponentiation (O(exp)).</p>
*
* For more information, please visit {@link https://en.wikipedia.org/wiki/Exponentiation_by_squaring}
*/
public final class FastExponentiation {

/**
* Private constructor to hide the implicit public one.
*/
private FastExponentiation() {
}

/**
* Performs fast exponentiation to calculate (base^exp) % mod using the method
* of exponentiation by squaring.
*
* <p>This method efficiently computes the result by squaring the base and halving
* the exponent at each step. It multiplies the base to the result when the exponent is odd.
*
* @param base the base number to be raised to the power of exp
* @param exp the exponent to which the base is raised
* @param mod the modulus to ensure the result does not overflow
* @return (base^exp) % mod
* @throws IllegalArgumentException if the modulus is less than or equal to 0
* @throws ArithmeticException if the exponent is negative (not supported in this implementation)
*/
public static long fastExponentiation(long base, long exp, long mod) {
if (mod <= 0) {
throw new IllegalArgumentException("Modulus must be positive.");
}

if (exp < 0) {
throw new ArithmeticException("Negative exponent is not supported.");
}

long result = 1;
base = base % mod; // Take the modulus of the base to handle large base values

// Fast exponentiation by squaring algorithm
while (exp > 0) {
// If exp is odd, multiply the base to the result
if ((exp & 1) == 1) { // exp & 1 checks if exp is odd
result = result * base % mod;
}
// Square the base and halve the exponent
base = base * base % mod; // base^2 % mod to avoid overflow
exp >>= 1; // Right shift exp to divide it by 2
}

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.thealgorithms.maths;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

/**
* Unit tests for the {@link FastExponentiation} class.
*
* <p>This class contains various test cases to verify the correctness of the fastExponentiation method.
* It covers basic functionality, edge cases, and exceptional cases.
*/
class FastExponentiationTest {

/**
* Tests fast exponentiation with small numbers.
*/
@Test
void testSmallNumbers() {
assertEquals(1024, FastExponentiation.fastExponentiation(2, 10, 10000), "2^10 mod 10000 should be 1024");
assertEquals(81, FastExponentiation.fastExponentiation(3, 4, 1000), "3^4 mod 1000 should be 81");
}

/**
* Tests the behavior of the fast exponentiation method when using a modulus.
*/
@Test
void testWithModulo() {
assertEquals(24, FastExponentiation.fastExponentiation(2, 10, 1000), "2^10 mod 1000 should be 24");
assertEquals(0, FastExponentiation.fastExponentiation(10, 5, 10), "10^5 mod 10 should be 0");
}

/**
* Tests the edge cases where base or exponent is 0.
*/
@Test
void testBaseCases() {
assertEquals(1, FastExponentiation.fastExponentiation(2, 0, 1000), "Any number raised to the power 0 mod anything should be 1");
assertEquals(0, FastExponentiation.fastExponentiation(0, 10, 1000), "0 raised to any power should be 0");
assertEquals(1, FastExponentiation.fastExponentiation(0, 0, 1000), "0^0 is considered 0 in modular arithmetic.");
}

/**
* Tests fast exponentiation with a negative base to ensure correctness under modular arithmetic.
*/
@Test
void testNegativeBase() {
assertEquals(9765625, FastExponentiation.fastExponentiation(-5, 10, 1000000007), "-5^10 mod 1000000007 should be 9765625");
}

/**
* Tests that a negative exponent throws an ArithmeticException.
*/
@Test
void testNegativeExponent() {
assertThrows(ArithmeticException.class, () -> { FastExponentiation.fastExponentiation(2, -5, 1000); });
}

/**
* Tests that the method throws an IllegalArgumentException for invalid modulus values.
*/
@Test
void testInvalidModulus() {
assertThrows(IllegalArgumentException.class, () -> { FastExponentiation.fastExponentiation(2, 5, 0); });
}
}