Skip to content

Add implementation of binary exponentiation #80

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
87 changes: 87 additions & 0 deletions src/main/kotlin/math/ShallowBinaryExponentiation.kt
Original file line number Diff line number Diff line change
@@ -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
}
}

45 changes: 45 additions & 0 deletions src/test/kotlin/math/ShallowBinaryExponentiationTest.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}