Skip to content

Commit 150efdc

Browse files
feat(core): implement log
1 parent fecdbc5 commit 150efdc

File tree

12 files changed

+171
-2
lines changed

12 files changed

+171
-2
lines changed

kbigint/src/androidMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package io.github.observeroftime.kbigint
55
import android.annotation.TargetApi
66
import android.os.Build.VERSION_CODES
77
import java.math.BigInteger
8+
import kotlin.math.floor
9+
import kotlin.math.log2
810

911
/** A multiplatform implementation of a big integer. */
1012
actual class KBigInt private constructor(private var value: BigInteger) : Comparable<KBigInt> {
@@ -130,6 +132,27 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
130132
@Throws(ArithmeticException::class)
131133
actual infix fun pow(n: Int) = KBigInt(value.pow(n))
132134

135+
/**
136+
* Compute the integer logarithm base [b] of the number.
137+
*
138+
* @since 0.5.0
139+
* @throws [ArithmeticException] if `this <= 0 || b < 2`
140+
*/
141+
@ExperimentalMultiplatform
142+
@Throws(ArithmeticException::class)
143+
actual infix fun log(b: Int): Int {
144+
if (value.signum() < 1 || b <= 1)
145+
throw ArithmeticException("Non-positive KBigInt or base < 2")
146+
147+
val guess = floor((bitLength - 1) / log2(b.toDouble())).toInt()
148+
val base = BigInteger.valueOf(b.toLong())
149+
val lowerBound = base.pow(guess)
150+
151+
return if (lowerBound > value) guess - 1
152+
else if (lowerBound.multiply(base) <= value) guess + 1
153+
else guess
154+
}
155+
133156
/**
134157
* Compute the approximate square root of the value.
135158
*

kbigint/src/androidUnitTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ actual class KBigIntTest {
9090
assertEquals(KBigInt("4611686018427387904"), long pow 2)
9191
}
9292

93+
@Test
94+
@OptIn(ExperimentalMultiplatform::class)
95+
actual fun testLog() {
96+
assertEquals(3, KBigInt(10) log 2)
97+
assertEquals(20, KBigInt("100000000000000000000") log 10)
98+
99+
assertFailsWith(ArithmeticException::class) {
100+
KBigInt(-1) log 2
101+
}
102+
103+
assertFailsWith(ArithmeticException::class) {
104+
KBigInt(2) log 1
105+
}
106+
}
107+
93108
@Test
94109
actual fun testSqrt() {
95110
assertEquals(KBigInt(46340), long.sqrt())

kbigint/src/commonMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,19 @@ expect class KBigInt : Comparable<KBigInt> {
102102
*/
103103
infix fun pow(n: Int): KBigInt
104104

105-
/** Compute the approximate square root of the value. */
105+
/**
106+
* Compute the integer logarithm base [b] of the number.
107+
*
108+
* @since 0.5.0
109+
* @throws [ArithmeticException] if `this <= 0 || b < 2`
110+
*/
111+
infix fun log(b: Int): Int
112+
113+
/**
114+
* Compute the approximate square root of the value.
115+
*
116+
* @throws [ArithmeticException] if the value is negative
117+
*/
106118
fun sqrt(): KBigInt
107119

108120
/**

kbigint/src/commonTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ expect class KBigIntTest {
1616
fun testGcdLcm()
1717
fun testPow()
1818
fun testSqrt()
19+
fun testLog()
1920
fun testAbs()
2021
fun testCompare()
2122
fun testEquals()

kbigint/src/javascript/kbigint-utils.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,23 @@ export function pow(value, n) {
102102
return value ** BigInt(n);
103103
}
104104

105+
/**
106+
* Compute the base `b` integer logarithm of the value.
107+
*
108+
* @param {bigint} value
109+
* @param {number} b
110+
* @returns {number}
111+
*/
112+
export function log(value, b) {
113+
const guess = ((bitLength(value) - 1) / Math.log2(b)) | 0;
114+
const base = BigInt(b)
115+
const lowerBound = base ** BigInt(guess);
116+
117+
if (lowerBound > value) return guess - 1;
118+
if (lowerBound * base <= value) return guess + 1;
119+
return guess;
120+
}
121+
105122
/**
106123
* Convert a {@link BigInt} to an {@link Int8Array}.
107124
*

kbigint/src/jsMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package io.github.observeroftime.kbigint
22

33
/** A multiplatform implementation of a big integer. */
44
@JsExport
5-
@Suppress("UNUSED_VARIABLE")
5+
@Suppress("UnusedVariable", "unused")
66
@OptIn(ExperimentalStdlibApi::class, ExperimentalJsExport::class)
77
actual class KBigInt private constructor(@JsExternalArgument private var value: BigInt) : Comparable<KBigInt> {
88
/** Convert a [String] to a [KBigInt]. */
@@ -186,6 +186,19 @@ actual class KBigInt private constructor(@JsExternalArgument private var value:
186186
return KBigInt(KBigIntUtils.pow(value, n))
187187
}
188188

189+
/**
190+
* Compute the integer logarithm base [b] of the number.
191+
*
192+
* @since 0.5.0
193+
* @throws [ArithmeticException] if `this <= 0 || b < 2`
194+
*/
195+
@ExperimentalMultiplatform
196+
actual infix fun log(b: Int): Int {
197+
if (sign < 1 || b <= 1)
198+
throw ArithmeticException("Non-positive KBigInt or base < 2")
199+
return KBigIntUtils.log(value, b)
200+
}
201+
189202
/**
190203
* Compute the approximate square root of the value.
191204
*

kbigint/src/jsMain/kotlin/io/github/observeroftime/kbigint/KBigIntUtils.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:Suppress("unused")
2+
13
package io.github.observeroftime.kbigint
24

35
internal external class BigInt {
@@ -17,6 +19,7 @@ internal external object KBigIntUtils {
1719
fun cmp(a: BigInt, b: BigInt): Int
1820
fun gcd(a: BigInt, b: BigInt): BigInt
1921
fun pow(value: BigInt, n: Int): BigInt
22+
fun log(value: BigInt, b: Int): Int
2023
fun toByteArray(value: BigInt): ByteArray
2124
fun fromByteArray(bytes: ByteArray): BigInt
2225
}

kbigint/src/jsTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ actual class KBigIntTest {
9191
assertEquals(KBigInt("4611686018427387904"), long pow 2)
9292
}
9393

94+
@Test
95+
@OptIn(ExperimentalMultiplatform::class)
96+
actual fun testLog() {
97+
assertEquals(3, KBigInt(10) log 2)
98+
assertEquals(20, KBigInt("100000000000000000000") log 10)
99+
100+
assertFailsWith(ArithmeticException::class) {
101+
KBigInt(-1) log 2
102+
}
103+
104+
assertFailsWith(ArithmeticException::class) {
105+
KBigInt(2) log 1
106+
}
107+
}
108+
94109
@Test
95110
@OptIn(ExperimentalMultiplatform::class)
96111
actual fun testSqrt() {

kbigint/src/jvmMain/kotlin/io/github/observeroftime/kbigint/KBigInt.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package io.github.observeroftime.kbigint
44

55
import java.math.BigInteger
6+
import kotlin.math.floor
7+
import kotlin.math.log2
68

79
/** A multiplatform implementation of a big integer. */
810
actual class KBigInt private constructor(private var value: BigInteger) : Comparable<KBigInt> {
@@ -128,6 +130,27 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
128130
@Throws(ArithmeticException::class)
129131
actual infix fun pow(n: Int) = KBigInt(value.pow(n))
130132

133+
/**
134+
* Compute the integer logarithm base [b] of the number.
135+
*
136+
* @since 0.5.0
137+
* @throws [ArithmeticException] if `this <= 0 || b < 2`
138+
*/
139+
@ExperimentalMultiplatform
140+
@Throws(ArithmeticException::class)
141+
actual infix fun log(b: Int): Int {
142+
if (value.signum() < 1 || b <= 1)
143+
throw ArithmeticException("Non-positive KBigInt or base < 2")
144+
145+
val guess = floor((bitLength - 1) / log2(b.toDouble())).toInt()
146+
val base = BigInteger.valueOf(b.toLong())
147+
val lowerBound = base.pow(guess)
148+
149+
return if (lowerBound > value) guess - 1
150+
else if (lowerBound.multiply(base) <= value) guess + 1
151+
else guess
152+
}
153+
131154
/**
132155
* Compute the approximate square root of the value.
133156
*

kbigint/src/jvmTest/kotlin/io/github/observeroftime/kbigint/KBigIntTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.observeroftime.kbigint
22

33
import kotlin.test.*
4+
import kotlin.test.Test
45

56
actual class KBigIntTest {
67
companion object {
@@ -90,6 +91,21 @@ actual class KBigIntTest {
9091
assertEquals(KBigInt("4611686018427387904"), long pow 2)
9192
}
9293

94+
@Test
95+
@OptIn(ExperimentalMultiplatform::class)
96+
actual fun testLog() {
97+
assertEquals(3, KBigInt(10) log 2)
98+
assertEquals(20, KBigInt("100000000000000000000") log 10)
99+
100+
assertFailsWith(ArithmeticException::class) {
101+
KBigInt(-1) log 2
102+
}
103+
104+
assertFailsWith(ArithmeticException::class) {
105+
KBigInt(2) log 1
106+
}
107+
}
108+
93109
@Test
94110
actual fun testSqrt() {
95111
assertEquals(KBigInt(46340), long.sqrt())

0 commit comments

Comments
 (0)