From a021d8f1404712f9cf137a16f051ac6d01a515b1 Mon Sep 17 00:00:00 2001 From: rahulc29 Date: Sun, 24 Oct 2021 15:18:04 +0530 Subject: [PATCH] Add tests and implementations of binary exponentiation algorithm The recursion is done shallowly, therefore, if the index of exponentiation is larger than 2 ^ 1000, this can cause a stack-overflow. --- .../math/ShallowBinaryExponentiation.kt | 87 +++++++++++++++++++ .../math/ShallowBinaryExponentiationTest.kt | 45 ++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/main/kotlin/math/ShallowBinaryExponentiation.kt create mode 100644 src/test/kotlin/math/ShallowBinaryExponentiationTest.kt diff --git a/src/main/kotlin/math/ShallowBinaryExponentiation.kt b/src/main/kotlin/math/ShallowBinaryExponentiation.kt new file mode 100644 index 0000000..f5734fe --- /dev/null +++ b/src/main/kotlin/math/ShallowBinaryExponentiation.kt @@ -0,0 +1,87 @@ +package math + +import java.math.BigInteger + +const val ERROR_MESSAGE_FOR_NEGATIVE_INDICES = "Binary Exponentiation cannot be used for negative indices" + +internal inline fun Long.toBigInteger(): BigInteger = BigInteger.valueOf(this) + +internal fun throwIllegalArgumentExceptionForNegativeIndices(): Nothing = + throw IllegalArgumentException(ERROR_MESSAGE_FOR_NEGATIVE_INDICES) + +/** + * Calculates a.pow(b) by using the algorithm of binary exponentiation, + * @param that allows for arbitrarily large indices. + * However, sufficiently large indices (as a rule of thumb : anything above 2 ^ 1000 as index) can cause a stack-overflow + * To prevent stack overflow and use arbitrarily large indices, use deep recursive binary exponentiation + * */ +infix fun BigInteger.bpow(that: BigInteger): BigInteger { + if (that < BigInteger.ZERO) { + throwIllegalArgumentExceptionForNegativeIndices() + } + if (that == BigInteger.ZERO) { + return BigInteger.ONE + } + val toSquare = this.bpow(that / 2.toBigInteger()) + return if (that % 2.toBigInteger() == BigInteger.ZERO) { + toSquare * toSquare + } else { + this * toSquare * toSquare + } +} + +/** + * Calculates a.pow(b) by using the algorithm of binary exponentiation, + * where a is the base and b is the index. + * If b is odd, a.pow(b) is written as a * (a.pow(b / 2)). + * If b is even, a.pow(b) is written as (a.pow(b / 2)). + * We compute (a.pow(b / 2)) recursively. + * Time Complexity : O(log(n)). + * Space Complexity : O(1). + * @see Long.bpow + * @see BigInteger.bpow + * @receiver the base of exponentiation + * @param that : the index of exponentiation + */ +infix fun Int.bpow(that: Int): Int { + if (that < 0) { + throwIllegalArgumentExceptionForNegativeIndices() + } + // a.pow(0) = 1 + if (that == 0) { + return 1 + } + + val toSquare = this.bpow(that / 2) + return if (that % 2 == 0) { + toSquare * toSquare + } else { + this * toSquare * toSquare + } +} + +/** + * Calculates a.pow(b) by using the algorithm of binary exponentiation + * Note that neither the [Int.bpow] nor [Long.bpow] are overflow-proof, + * they use native ints (32-bit signed integers) and longs (64-bit signed integers). + * To use overflow-proof exponentiation, use [BigInteger.bpow] + * @see Int.bpow(that) + * @see BigInteger.bpow + * @receiver the base of exponentiation + * @param that : the index of exponentiation + * */ +infix fun Long.bpow(that: Long): Long { + if (that < 0L) { + throwIllegalArgumentExceptionForNegativeIndices() + } + if (that == 0L) { + return 1L + } + val toSquare = this.bpow(that / 2) + return if (that % 2L == 0L) { + toSquare * toSquare + } else { + this * toSquare * toSquare + } +} + diff --git a/src/test/kotlin/math/ShallowBinaryExponentiationTest.kt b/src/test/kotlin/math/ShallowBinaryExponentiationTest.kt new file mode 100644 index 0000000..db467d3 --- /dev/null +++ b/src/test/kotlin/math/ShallowBinaryExponentiationTest.kt @@ -0,0 +1,45 @@ +package math + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.math.BigInteger + +class ShallowBinaryExponentiationTest { + companion object { + const val VERY_LARGE_RESULT_OF_EXPONENTIATION = + "22745166391733635518864036952592070133430143938205371357914689788524029686058206121982289842297958648295318631512712290346668320656359121380034172922949409836427358375009684698518997958656" + } + + @Test + fun testBinaryExponentiationOfBigIntegers() { + assertEquals(125L.toBigInteger(), 5.toBigInteger() bpow 3.toBigInteger()) + assertEquals( + BigInteger("16832483071748016388427036991744"), + BigInteger("4102740921840912").bpow(2.toBigInteger()) + ) + assertEquals( + BigInteger(VERY_LARGE_RESULT_OF_EXPONENTIATION), + BigInteger("4102740921840912").bpow(BigInteger("12")) + ) + } + + @Test + fun testBinaryExponentiationOfInts() { + assertEquals(125, 5 bpow 3) + assertEquals(1073741824, 32768 bpow 2) + } + + @Test(expected = StackOverflowError::class) + fun testStackOverflowForVeryLargeIndex() { + BigInteger.ONE.bpow( + 2.toBigInteger().bpow(5000.toBigInteger()) + ) + } + + @Test + fun testBinaryExponentiationOfLongs() { + assertEquals(125L, 5L bpow 3L) + assertEquals(1073741824L, 32768L bpow 2L) + assertEquals(1152921504606846976L, 1024L bpow 6L) + } +} \ No newline at end of file