diff --git a/src/main/java/com/thealgorithms/maths/FastExponentiation.java b/src/main/java/com/thealgorithms/maths/FastExponentiation.java new file mode 100644 index 000000000000..27f49e27ff30 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/FastExponentiation.java @@ -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. + * + *

The algorithm works by repeatedly squaring the base and reducing the exponent + * by half at each step. It exploits the fact that: + *

+ * The result is computed modulo `mod` at each step to avoid overflow and keep the result within bounds. + *

+ * + *

Time complexity: O(log(exp)) — much faster than naive exponentiation (O(exp)).

+ * + * 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. + * + *

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; + } +} diff --git a/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java b/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java new file mode 100644 index 000000000000..f117f90233e3 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java @@ -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. + * + *

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); }); + } +}