Skip to content

Commit abcfd05

Browse files
feat(core): implement root
1 parent 9bf54df commit abcfd05

File tree

14 files changed

+222
-23
lines changed

14 files changed

+222
-23
lines changed

.github/detekt.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
complexity:
22
TooManyFunctions:
33
active: false
4+
CyclomaticComplexMethod:
5+
threshold: 20
46

57
style:
68
WildcardImport:
79
active: false
810
ForbiddenComment:
911
active: false
12+
ReturnCount:
13+
max: 3
1014
UnusedPrivateProperty:
1115
excludes:
1216
- "**/commonMain/**"
@@ -18,6 +22,7 @@ style:
1822
- "-1"
1923
- "0"
2024
- "1"
25+
- "2"
2126
- "10"
2227
ignoreAnnotated:
2328
- Test

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ 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.ArithmeticException
89
import kotlin.math.floor
910
import kotlin.math.log2
1011

@@ -148,7 +149,7 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
148149
* Compute the integer logarithm base [b] of the number.
149150
*
150151
* @since 0.5.0
151-
* @throws [ArithmeticException] if `this <= 0 || b < 2`
152+
* @throws [ArithmeticException] if the value is `0` or negative, or `b < 2`
152153
*/
153154
@ExperimentalMultiplatform
154155
@Throws(ArithmeticException::class)
@@ -165,6 +166,41 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
165166
else guess
166167
}
167168

169+
/**
170+
* Compute the approximate [n]-th root of the value.
171+
*
172+
* @since 0.5.0
173+
*/
174+
@ExperimentalStdlibApi
175+
actual infix fun root(n: Int): KBigInt {
176+
if (n <= 0) throw ArithmeticException("Non-positive root")
177+
if (sign == -1 && (n and 1) == 0)
178+
throw ArithmeticException("Even root of negative number")
179+
if (n == 1 || sign == 0 || this == KBigInt(1)) return this
180+
181+
var low = BigInteger.ZERO
182+
var high = value.abs()
183+
var mid: BigInteger
184+
185+
while (low <= high) {
186+
mid = (low + high) / BigInteger.valueOf(2L)
187+
when (mid.pow(n).compareTo(value.abs())) {
188+
-1 -> {
189+
low = mid + BigInteger.ONE
190+
}
191+
1 -> {
192+
high = mid - BigInteger.ONE
193+
}
194+
else -> {
195+
high = mid
196+
break
197+
}
198+
}
199+
}
200+
201+
return KBigInt(if (sign == -1) -high else high)
202+
}
203+
168204
/**
169205
* Compute the approximate square root of the value.
170206
*

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

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

95+
@Test
96+
@OptIn(ExperimentalStdlibApi::class)
97+
actual fun testRoot() {
98+
assertEquals(KBigInt("100"), KBigInt("1000000") root 3)
99+
assertEquals(KBigInt("-10"), KBigInt("-100000") root 5)
100+
101+
assertFailsWith(ArithmeticException::class) {
102+
KBigInt("-1000000") root 4
103+
}
104+
105+
assertFailsWith(ArithmeticException::class) {
106+
KBigInt(1) root -1
107+
}
108+
}
109+
95110
@Test
96111
@OptIn(ExperimentalMultiplatform::class)
97112
actual fun testLog() {

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,18 @@ expect class KBigInt : Comparable<KBigInt> {
114114
* Compute the integer logarithm base [b] of the number.
115115
*
116116
* @since 0.5.0
117-
* @throws [ArithmeticException] if `this <= 0 || b < 2`
117+
* @throws [ArithmeticException] if the value is `0` or negative, or `b < 2`
118118
*/
119119
infix fun log(b: Int): Int
120120

121+
/**
122+
* Compute the approximate [n]-th root of the value.
123+
*
124+
* @since 0.5.0
125+
* @throws [ArithmeticException] if the value is negative and `n` is positive, or `n <= 0`
126+
*/
127+
infix fun root(n: Int): KBigInt
128+
121129
/**
122130
* Compute the approximate square root of the value.
123131
*

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ expect class KBigIntTest {
1515
fun testInvert()
1616
fun testGcdLcm()
1717
fun testPow()
18+
fun testRoot()
1819
fun testSqrt()
1920
fun testLog()
2021
fun testAbs()

kbigint/src/javascript/kbigint-utils.mjs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,35 @@ export function abs(value) {
5656
return value < 0n ? -value : value;
5757
}
5858

59+
/**
60+
* Compute the `k`-th root of the number `n`.
61+
*
62+
* @param {bigint} value
63+
* @param {bigint} n
64+
* @returns {bigint}
65+
*/
66+
export function root(value, n) {
67+
if (value === 0n || value === 1n) return value;
68+
const x = abs(value);
69+
let low = 0n, high = x, mid;
70+
71+
while (low <= high) {
72+
mid = (low + high) / 2n;
73+
switch (cmp(mid ** n, x)) {
74+
case -1:
75+
low = mid + 1n;
76+
break;
77+
case 1:
78+
high = mid - 1n;
79+
break;
80+
default:
81+
return value < 0n ? -mid : mid;
82+
}
83+
}
84+
85+
return value < 0n ? -high : high;
86+
}
87+
5988
/**
6089
* Compute the approximate square root of the value.
6190
*
@@ -64,7 +93,7 @@ export function abs(value) {
6493
*/
6594
export function sqrt(value) {
6695
if (value < 2n) return value;
67-
if (value >= 16n) newtonRoot(value, 1n);
96+
if (value >= 16n) return root(value, 2n);
6897
return BigInt(Math.sqrt(Number(value)) | 0);
6998
}
7099

@@ -153,18 +182,6 @@ export function fromByteArray(bytes) {
153182
return hexToBn(hex.join(''));
154183
}
155184

156-
/**
157-
* @private
158-
* @param {bigint} n
159-
* @param {bigint} k
160-
* @returns {bigint}
161-
* @see https://stackoverflow.com/a/53684036/21974435
162-
*/
163-
function newtonRoot(n, k) {
164-
const x = ((n / k) + k) >> 1n;
165-
return k === x || k === (x - 1n) ? k : newtonRoot(n, x);
166-
}
167-
168185
/**
169186
* @private
170187
* @param {string} str

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,17 +196,30 @@ actual class KBigInt private constructor(@JsExternalArgument private var value:
196196
* @throws [ArithmeticException] if [n] is negative
197197
*/
198198
actual infix fun pow(n: Int): KBigInt {
199-
if (n < 0)
200-
throw ArithmeticException("Negative exponent")
199+
if (n < 0) throw ArithmeticException("Negative exponent")
201200
// FIXME: https://youtrack.jetbrains.com/issue/KT-60221/
202201
return KBigInt(KBigIntUtils.pow(value, n))
203202
}
204203

204+
/**
205+
* Compute the approximate [n]-th root of the value.
206+
*
207+
* @since 0.5.0
208+
* @throws [ArithmeticException] if the value is negative and `n` is positive, or `n <= 0`
209+
*/
210+
@ExperimentalStdlibApi
211+
actual infix fun root(n: Int): KBigInt {
212+
if (n <= 0) throw ArithmeticException("Non-positive root")
213+
if (sign == -1 && (n and 1) == 0)
214+
throw ArithmeticException("Even root of negative number")
215+
return KBigInt(KBigIntUtils.root(value, BigInt(n)))
216+
}
217+
205218
/**
206219
* Compute the integer logarithm base [b] of the number.
207220
*
208221
* @since 0.5.0
209-
* @throws [ArithmeticException] if `this <= 0 || b < 2`
222+
* @throws [ArithmeticException] if the value is `0` or negative, or `b < 2`
210223
*/
211224
@ExperimentalMultiplatform
212225
actual infix fun log(b: Int): Int {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal external object KBigIntUtils {
1919
fun cmp(a: BigInt, b: BigInt): Int
2020
fun gcd(a: BigInt, b: BigInt): BigInt
2121
fun pow(value: BigInt, n: Int): BigInt
22+
fun root(value: BigInt, n: BigInt): BigInt
2223
fun log(value: BigInt, b: Int): Int
2324
fun toByteArray(value: BigInt): ByteArray
2425
fun fromByteArray(bytes: ByteArray): BigInt

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

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

96+
@Test
97+
@OptIn(ExperimentalStdlibApi::class)
98+
actual fun testRoot() {
99+
assertEquals(KBigInt("100"), KBigInt("1000000") root 3)
100+
assertEquals(KBigInt("-10"), KBigInt("-100000") root 5)
101+
102+
assertFailsWith(ArithmeticException::class) {
103+
KBigInt("-1000000") root 4
104+
}
105+
106+
assertFailsWith(ArithmeticException::class) {
107+
KBigInt(1) root -1
108+
}
109+
}
110+
96111
@Test
97112
@OptIn(ExperimentalMultiplatform::class)
98113
actual fun testLog() {

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
146146
* Compute the integer logarithm base [b] of the number.
147147
*
148148
* @since 0.5.0
149-
* @throws [ArithmeticException] if `this <= 0 || b < 2`
149+
* @throws [ArithmeticException] if the value is `0` or negative, or `b < 2`
150150
*/
151151
@ExperimentalMultiplatform
152152
@Throws(ArithmeticException::class)
@@ -163,6 +163,44 @@ actual class KBigInt private constructor(private var value: BigInteger) : Compar
163163
else guess
164164
}
165165

166+
/**
167+
* Compute the approximate [n]-th root of the value.
168+
*
169+
* @since 0.5.0
170+
* @throws [ArithmeticException] if the value is negative and `n` is positive, or `n <= 0`
171+
*/
172+
@ExperimentalStdlibApi
173+
@Throws(ArithmeticException::class)
174+
actual infix fun root(n: Int): KBigInt {
175+
if (n <= 0) throw ArithmeticException("Non-positive root")
176+
if (sign == -1 && (n and 1) == 0)
177+
throw ArithmeticException("Even root of negative number")
178+
if (n == 1 || sign == 0 || this == KBigInt(1)) return this
179+
if (n == 2) return sqrt()
180+
181+
var low = BigInteger.ZERO
182+
var high = value.abs()
183+
var mid: BigInteger
184+
185+
while (low <= high) {
186+
mid = (low + high) / BigInteger.valueOf(2L)
187+
when (mid.pow(n).compareTo(value.abs())) {
188+
-1 -> {
189+
low = mid + BigInteger.ONE
190+
}
191+
1 -> {
192+
high = mid - BigInteger.ONE
193+
}
194+
else -> {
195+
high = mid
196+
break
197+
}
198+
}
199+
}
200+
201+
return KBigInt(if (sign == -1) -high else high)
202+
}
203+
166204
/**
167205
* Compute the approximate square root of the value.
168206
*

0 commit comments

Comments
 (0)