From 1716b7f5d1d871610961419fd425c49730ea1a2f Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Mon, 10 May 2021 17:57:49 +0300 Subject: [PATCH 01/14] Fast power is added --- .../kscience/kmath/benchmarks/BigIntBenchmark.kt | 6 ++---- .../space/kscience/kmath/operations/Algebra.kt | 15 +++++++++++++++ .../kmath/operations/BigIntAlgebraTest.kt | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt index b9a10c774..33c659fca 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt @@ -15,15 +15,13 @@ import space.kscience.kmath.operations.BigIntField import space.kscience.kmath.operations.JBigIntegerField import space.kscience.kmath.operations.invoke -private fun BigInt.pow(power: Int): BigInt = modPow(BigIntField.number(power), BigInt.ONE) - @State(Scope.Benchmark) internal class BigIntBenchmark { val kmNumber = BigIntField.number(Int.MAX_VALUE) val jvmNumber = JBigIntegerField.number(Int.MAX_VALUE) - val largeKmNumber = BigIntField { number(11).pow(100_000) } + val largeKmNumber = BigIntField { number(11).pow(100_000UL) } val largeJvmNumber = JBigIntegerField { number(11).pow(100_000) } val bigExponent = 50_000 @@ -59,7 +57,7 @@ internal class BigIntBenchmark { @Benchmark fun kmPower(blackhole: Blackhole) = BigIntField { - blackhole.consume(kmNumber.pow(bigExponent)) + blackhole.consume(kmNumber.pow(bigExponent.toULong())) } @Benchmark diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index 0334e0466..49861b543 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -250,6 +250,21 @@ public interface Ring : Group, RingOperations { * neutral operation for multiplication */ public val one: T + + public fun T.pow(exponent: ULong): T = when { + this == zero && exponent > 0UL -> zero + this == one -> this + else -> powWithoutOptimization(exponent) + } + + private fun T.powWithoutOptimization(exponent: ULong): T = when (exponent) { + 0UL -> one + 1UL -> this + else -> { + val pre = powWithoutOptimization(exponent shr 1).let { it * it } + if (exponent and 1UL == 0UL) pre else pre * this + } + } } /** diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt index 5df89a385..05b73b49d 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.operations import space.kscience.kmath.testutils.RingVerifier +import kotlin.math.pow import kotlin.test.Test import kotlin.test.assertEquals @@ -21,6 +22,19 @@ internal class BigIntAlgebraTest { assertEquals(res, 1_000_000.toBigInt()) } + @Test + fun testKBigIntegerRingPow() { + BigIntField { + for (num in 0..5) + for (exponent in 0UL..10UL) + assertEquals( + num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), + num.toBigInt().pow(exponent), + "$num ^ $exponent" + ) + } + } + @Test fun testKBigIntegerRingSum_100_000_000__100_000_000() { BigIntField { From d17a3ac6eff3d6c62e99af7e030010846afd413b Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Mon, 10 May 2021 18:24:13 +0300 Subject: [PATCH 02/14] Further optimization of power is added --- .../kotlin/space/kscience/kmath/operations/Algebra.kt | 1 + .../kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index 49861b543..f7c021998 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -254,6 +254,7 @@ public interface Ring : Group, RingOperations { public fun T.pow(exponent: ULong): T = when { this == zero && exponent > 0UL -> zero this == one -> this + this == -one -> powWithoutOptimization(exponent % 2UL) else -> powWithoutOptimization(exponent) } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt index 05b73b49d..90cd29758 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt @@ -25,7 +25,7 @@ internal class BigIntAlgebraTest { @Test fun testKBigIntegerRingPow() { BigIntField { - for (num in 0..5) + for (num in -5..5) for (exponent in 0UL..10UL) assertEquals( num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), From 3ad7f32ada115b279e00edc49f5e9ba7e63a834d Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Mon, 10 May 2021 18:41:40 +0300 Subject: [PATCH 03/14] Incorrect BigInt equality fixed --- .../kotlin/space/kscience/kmath/operations/BigInt.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index e74efa9ca..e81f1686d 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -56,8 +56,7 @@ public class BigInt internal constructor( else -> sign * compareMagnitudes(magnitude, other.magnitude) } - public override fun equals(other: Any?): Boolean = - if (other is BigInt) compareTo(other) == 0 else error("Can't compare KBigInteger to a different type") + public override fun equals(other: Any?): Boolean = other is BigInt && compareTo(other) == 0 public override fun hashCode(): Int = magnitude.hashCode() + sign From 2da0648d73829396777066f970bf861e75410f01 Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Tue, 11 May 2021 01:40:45 +0300 Subject: [PATCH 04/14] Karatsuba added Incorrect equals fix test Incorrect overflow handling support --- .../space/kscience/kmath/operations/BigInt.kt | 46 +++++++++++++++---- .../kmath/operations/BigIntOperationsTest.kt | 25 +++++++++- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index e81f1686d..c7079902b 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -86,20 +86,23 @@ public class BigInt internal constructor( public operator fun times(b: BigInt): BigInt = when { this.sign == 0.toByte() -> ZERO b.sign == 0.toByte() -> ZERO -// TODO: Karatsuba + b.magnitude.size == 1 -> this * b.magnitude[0] * b.sign.toInt() + this.magnitude.size == 1 -> b * this.magnitude[0] * this.sign.toInt() else -> BigInt((this.sign * b.sign).toByte(), multiplyMagnitudes(this.magnitude, b.magnitude)) } public operator fun times(other: UInt): BigInt = when { sign == 0.toByte() -> ZERO other == 0U -> ZERO + other == 1U -> this else -> BigInt(sign, multiplyMagnitudeByUInt(magnitude, other)) } - public operator fun times(other: Int): BigInt = if (other > 0) - this * kotlin.math.abs(other).toUInt() - else - -this * kotlin.math.abs(other).toUInt() + public operator fun times(other: Int): BigInt = when { + other > 0 -> this * kotlin.math.abs(other).toUInt() + other != Int.MIN_VALUE -> -this * kotlin.math.abs(other).toUInt() + else -> times(other.toBigInt()) + } public operator fun div(other: UInt): BigInt = BigInt(this.sign, divideMagnitudeByUInt(this.magnitude, other)) @@ -237,6 +240,7 @@ public class BigInt internal constructor( public const val BASE_SIZE: Int = 32 public val ZERO: BigInt = BigInt(0, uintArrayOf()) public val ONE: BigInt = BigInt(1, uintArrayOf(1u)) + private const val KARATSUBA_THRESHOLD = 80 private val hexMapping: HashMap = hashMapOf( 0U to "0", 1U to "1", 2U to "2", 3U to "3", @@ -317,7 +321,16 @@ public class BigInt internal constructor( return stripLeadingZeros(result) } - private fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { + internal fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude = when { + mag1.size + mag2.size < KARATSUBA_THRESHOLD || mag1.isEmpty() || mag2.isEmpty() -> naiveMultiplyMagnitudes( + mag1, + mag2 + ) + // TODO implement Fourier + else -> karatsubaMultiplyMagnitudes(mag1, mag2) + } + + internal fun naiveMultiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { val resultLength = mag1.size + mag2.size val result = Magnitude(resultLength) @@ -336,6 +349,21 @@ public class BigInt internal constructor( return stripLeadingZeros(result) } + internal fun karatsubaMultiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude { + //https://en.wikipedia.org/wiki/Karatsuba_algorithm + val halfSize = min(mag1.size, mag2.size) / 2 + val x0 = mag1.sliceArray(0 until halfSize).toBigInt(1) + val x1 = mag1.sliceArray(halfSize until mag1.size).toBigInt(1) + val y0 = mag2.sliceArray(0 until halfSize).toBigInt(1) + val y1 = mag2.sliceArray(halfSize until mag2.size).toBigInt(1) + + val z0 = x0 * y0 + val z2 = x1 * y1 + val z1 = (x0 + x1) * (y1 + y0) - z0 - z2 + + return (z2.shl(2 * halfSize * BASE_SIZE) + z1.shl(halfSize * BASE_SIZE) + z0).magnitude + } + private fun divideMagnitudeByUInt(mag: Magnitude, x: UInt): Magnitude { val resultLength = mag.size val result = Magnitude(resultLength) @@ -426,7 +454,7 @@ private val hexChToInt: MutableMap = hashMapOf( public fun String.parseBigInteger(): BigInt? { val sign: Int val sPositive: String - + //TODO substring = O(n). Can be replaced by some drop that is O(1) when { this[0] == '+' -> { sign = +1 @@ -446,8 +474,10 @@ public fun String.parseBigInteger(): BigInt? { var digitValue = BigInt.ONE val sPositiveUpper = sPositive.uppercase() - if (sPositiveUpper.startsWith("0X")) { // hex representation + if (sPositiveUpper.startsWith("0X")) { + // hex representation val sHex = sPositiveUpper.substring(2) + // TODO optimize O(n2) -> O(n) for (ch in sHex.reversed()) { if (ch == '_') continue diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt index ae34dbc04..9f35f7a69 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntOperationsTest.kt @@ -5,8 +5,9 @@ package space.kscience.kmath.operations -import kotlin.test.Test -import kotlin.test.assertEquals +import kotlin.random.Random +import kotlin.random.nextUInt +import kotlin.test.* @kotlin.ExperimentalUnsignedTypes class BigIntOperationsTest { @@ -150,6 +151,18 @@ class BigIntOperationsTest { assertEquals(prod, res) } + @Test + fun testKaratsuba() { + val x = uintArrayOf(12U, 345U) + val y = uintArrayOf(6U, 789U) + assertContentEquals(BigInt.naiveMultiplyMagnitudes(x, y), BigInt.karatsubaMultiplyMagnitudes(x, y)) + repeat(1000) { + val x1 = UIntArray(Random.nextInt(100, 1000)) { Random.nextUInt() } + val y1 = UIntArray(Random.nextInt(100, 1000)) { Random.nextUInt() } + assertContentEquals(BigInt.naiveMultiplyMagnitudes(x1, y1), BigInt.karatsubaMultiplyMagnitudes(x1, y1)) + } + } + @Test fun test_shr_20() { val x = 20.toBigInt() @@ -383,4 +396,12 @@ class BigIntOperationsTest { return assertEquals(res, x % mod) } + + @Test + fun testNotEqualsOtherTypeInstanceButButNotFails() = assertFalse(0.toBigInt().equals("")) + + @Test + fun testIntAbsOverflow() { + assertEquals((-Int.MAX_VALUE.toLong().toBigInt() - 1.toBigInt()) * 2, 2.toBigInt() * Int.MIN_VALUE) + } } From 74d14970b9e50e808c8901b8d2850ecd29f79ba1 Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Wed, 12 May 2021 00:58:27 +0300 Subject: [PATCH 05/14] empty string parsing fixed --- .../space/kscience/kmath/operations/BigInt.kt | 7 +++++- .../kmath/operations/BigIntConversionsTest.kt | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index c7079902b..00b9f9fc2 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -452,6 +452,7 @@ private val hexChToInt: MutableMap = hashMapOf( * Returns null if a valid number can not be read from a string */ public fun String.parseBigInteger(): BigInt? { + if (this.isEmpty()) return null val sign: Int val sPositive: String //TODO substring = O(n). Can be replaced by some drop that is O(1) @@ -473,15 +474,18 @@ public fun String.parseBigInteger(): BigInt? { var res = BigInt.ZERO var digitValue = BigInt.ONE val sPositiveUpper = sPositive.uppercase() + var isEmpty = true if (sPositiveUpper.startsWith("0X")) { // hex representation val sHex = sPositiveUpper.substring(2) + if (this.isEmpty()) return null // TODO optimize O(n2) -> O(n) for (ch in sHex.reversed()) { if (ch == '_') continue res += digitValue * (hexChToInt[ch] ?: return null) + isEmpty = false digitValue *= 16.toBigInt() } } else for (ch in sPositiveUpper.reversed()) { @@ -491,10 +495,11 @@ public fun String.parseBigInteger(): BigInt? { return null } res += digitValue * (ch.code - '0'.code) + isEmpty = false digitValue *= 10.toBigInt() } - return res * sign + return if (isEmpty) null else res * sign } public inline fun Buffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): Buffer = diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt index a2832e531..1feaae808 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt @@ -7,9 +7,31 @@ package space.kscience.kmath.operations import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull @kotlin.ExperimentalUnsignedTypes class BigIntConversionsTest { + + @Test + fun emptyString() { + assertNull("".parseBigInteger()) + assertNull("+".parseBigInteger()) + assertNull("-".parseBigInteger()) + + assertNull("0x".parseBigInteger()) + assertNull("+0x".parseBigInteger()) + assertNull("-0x".parseBigInteger()) + + + assertNull("_".parseBigInteger()) + assertNull("+_".parseBigInteger()) + assertNull("-_".parseBigInteger()) + + assertNull("0x_".parseBigInteger()) + assertNull("+0x_".parseBigInteger()) + assertNull("-0x_".parseBigInteger()) + } + @Test fun testToString0x10() { val x = 0x10.toBigInt() From 9184bd55de46bcb16a5c33b8a02f604b592aff4a Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Wed, 12 May 2021 02:01:49 +0300 Subject: [PATCH 06/14] Parser optimization --- .../space/kscience/kmath/operations/BigInt.kt | 90 +++++++++++-------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index 00b9f9fc2..ab05d079f 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -441,11 +441,11 @@ public fun UIntArray.toBigInt(sign: Byte): BigInt { return BigInt(sign, copyOf()) } -private val hexChToInt: MutableMap = hashMapOf( - '0' to 0, '1' to 1, '2' to 2, '3' to 3, - '4' to 4, '5' to 5, '6' to 6, '7' to 7, - '8' to 8, '9' to 9, 'A' to 10, 'B' to 11, - 'C' to 12, 'D' to 13, 'E' to 14, 'F' to 15 +private val hexChToInt: MutableMap = hashMapOf( + '0' to 0U, '1' to 1U, '2' to 2U, '3' to 3U, + '4' to 4U, '5' to 5U, '6' to 6U, '7' to 7U, + '8' to 8U, '9' to 9U, 'A' to 10U, 'B' to 11U, + 'C' to 12U, 'D' to 13U, 'E' to 14U, 'F' to 15U ) /** @@ -454,52 +454,72 @@ private val hexChToInt: MutableMap = hashMapOf( public fun String.parseBigInteger(): BigInt? { if (this.isEmpty()) return null val sign: Int - val sPositive: String - //TODO substring = O(n). Can be replaced by some drop that is O(1) - when { - this[0] == '+' -> { + + val positivePartIndex = when (this[0]) { + '+' -> { sign = +1 - sPositive = this.substring(1) + 1 } - this[0] == '-' -> { + '-' -> { sign = -1 - sPositive = this.substring(1) + 1 } else -> { - sPositive = this sign = +1 + 0 } } - var res = BigInt.ZERO - var digitValue = BigInt.ONE - val sPositiveUpper = sPositive.uppercase() var isEmpty = true - if (sPositiveUpper.startsWith("0X")) { + return if (this.startsWith("0X", startIndex = positivePartIndex, ignoreCase = true)) { // hex representation - val sHex = sPositiveUpper.substring(2) - if (this.isEmpty()) return null - // TODO optimize O(n2) -> O(n) - for (ch in sHex.reversed()) { - if (ch == '_') continue - res += digitValue * (hexChToInt[ch] ?: return null) + val uInts = mutableListOf(0U) + var offset = 0 + fun addDigit(value: UInt) { + uInts[uInts.lastIndex] += value shl offset + offset += 4 + if (offset == 32) { + uInts.add(0U) + offset = 0 + } + } + + for (index in lastIndex downTo positivePartIndex + 2) { + when (val ch = this[index]) { + '_' -> continue + in '0'..'9' -> addDigit((ch - '0').toUInt()) + in 'A'..'F' -> addDigit((ch - 'A').toUInt() + 10U) + in 'a'..'f' -> addDigit((ch - 'a').toUInt() + 10U) + else -> return null + } isEmpty = false - digitValue *= 16.toBigInt() } - } else for (ch in sPositiveUpper.reversed()) { - // decimal representation - if (ch == '_') continue - if (ch !in '0'..'9') { - return null - } - res += digitValue * (ch.code - '0'.code) - isEmpty = false - digitValue *= 10.toBigInt() - } - return if (isEmpty) null else res * sign + while (uInts.isNotEmpty() && uInts.last() == 0U) + uInts.removeLast() + + if (isEmpty) null else BigInt(sign.toByte(), uInts.toUIntArray()) + } else { + // hex representation + var res = BigInt.ZERO + var digitValue = BigInt.ONE + for (index in lastIndex downTo positivePartIndex) { + val ch = this[index] + // decimal representation + if (ch == '_') continue + if (ch !in '0'..'9') { + return null + } + res += digitValue * (ch.code - '0'.code) + isEmpty = false + digitValue *= 10.toBigInt() + + } + + if (isEmpty) null else res * sign + } } public inline fun Buffer.Companion.bigInt(size: Int, initializer: (Int) -> BigInt): Buffer = From b4e47494fcf6683d14d523aaaba34e33da592c3c Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Wed, 12 May 2021 04:42:17 +0300 Subject: [PATCH 07/14] Parsing of decimal values takes 9 immediately digits but not 1. --- .../kmath/benchmarks/BigIntBenchmark.kt | 25 ++++++++-- .../space/kscience/kmath/operations/BigInt.kt | 47 ++++++++++--------- .../kmath/operations/BigIntConversionsTest.kt | 8 +++- 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt index 33c659fca..639eaacf3 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt @@ -10,10 +10,7 @@ import kotlinx.benchmark.Blackhole import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.State -import space.kscience.kmath.operations.BigInt -import space.kscience.kmath.operations.BigIntField -import space.kscience.kmath.operations.JBigIntegerField -import space.kscience.kmath.operations.invoke +import space.kscience.kmath.operations.* @State(Scope.Benchmark) @@ -64,4 +61,24 @@ internal class BigIntBenchmark { fun jvmPower(blackhole: Blackhole) = JBigIntegerField { blackhole.consume(jvmNumber.pow(bigExponent)) } + + @Benchmark + fun kmParsing16(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("0x7f57ed8b89c29a3b9a85c7a5b84ca3929c7b7488593".parseBigInteger()) + } + + @Benchmark + fun kmParsing10(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("236656783929183747565738292847574838922010".parseBigInteger()) + } + + @Benchmark + fun jvmParsing10(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("236656783929183747565738292847574838922010".toBigInteger(10)) + } + + @Benchmark + fun jvmParsing16(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume("7f57ed8b89c29a3b9a85c7a5b84ca3929c7b7488593".toBigInteger(16)) + } } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index ab05d079f..780a4591d 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -441,13 +441,6 @@ public fun UIntArray.toBigInt(sign: Byte): BigInt { return BigInt(sign, copyOf()) } -private val hexChToInt: MutableMap = hashMapOf( - '0' to 0U, '1' to 1U, '2' to 2U, '3' to 3U, - '4' to 4U, '5' to 5U, '6' to 6U, '7' to 7U, - '8' to 8U, '9' to 9U, 'A' to 10U, 'B' to 11U, - 'C' to 12U, 'D' to 13U, 'E' to 14U, 'F' to 15U -) - /** * Returns null if a valid number can not be read from a string */ @@ -475,7 +468,7 @@ public fun String.parseBigInteger(): BigInt? { return if (this.startsWith("0X", startIndex = positivePartIndex, ignoreCase = true)) { // hex representation - val uInts = mutableListOf(0U) + val uInts = ArrayList(length).apply { add(0U) } var offset = 0 fun addDigit(value: UInt) { uInts[uInts.lastIndex] += value shl offset @@ -502,22 +495,34 @@ public fun String.parseBigInteger(): BigInt? { if (isEmpty) null else BigInt(sign.toByte(), uInts.toUIntArray()) } else { - // hex representation - var res = BigInt.ZERO - var digitValue = BigInt.ONE - for (index in lastIndex downTo positivePartIndex) { - val ch = this[index] - // decimal representation - if (ch == '_') continue - if (ch !in '0'..'9') { - return null - } - res += digitValue * (ch.code - '0'.code) - isEmpty = false - digitValue *= 10.toBigInt() + // decimal representation + val positivePart = buildList(length) { + for (index in positivePartIndex until length) + when (val a = this@parseBigInteger[index]) { + '_' -> continue + in '0'..'9' -> add(a) + else -> return null + } } + val offset = positivePart.size % 9 + isEmpty = offset == 0 + + fun parseUInt(fromIndex: Int, toIndex: Int): UInt? { + var res = 0U + for (i in fromIndex until toIndex) { + res = res * 10U + (positivePart[i].digitToIntOrNull()?.toUInt() ?: return null) + } + return res + } + + var res = parseUInt(0, offset)?.toBigInt() ?: return null + + for (index in offset..positivePart.lastIndex step 9) { + isEmpty = false + res = res * 1_000_000_000U + (parseUInt(index, index + 9) ?: return null).toBigInt() + } if (isEmpty) null else res * sign } } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt index 1feaae808..85f368f3e 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntConversionsTest.kt @@ -13,7 +13,7 @@ import kotlin.test.assertNull class BigIntConversionsTest { @Test - fun emptyString() { + fun testEmptyString() { assertNull("".parseBigInteger()) assertNull("+".parseBigInteger()) assertNull("-".parseBigInteger()) @@ -38,6 +38,12 @@ class BigIntConversionsTest { assertEquals("0x10", x.toString()) } + @Test + fun testUnderscores() { + assertEquals("0x10", "0x_1_0_".parseBigInteger().toString()) + assertEquals("0xa", "_1_0_".parseBigInteger().toString()) + } + @Test fun testToString0x17ffffffd() { val x = 0x17ffffffdL.toBigInt() From ed86a31c50d7d6b3890342fb2652ffdba4630fb0 Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Wed, 12 May 2021 04:58:48 +0300 Subject: [PATCH 08/14] Placing pow outside the Ring interface. --- .../kscience/kmath/operations/Algebra.kt | 26 +++++++++---------- .../space/kscience/kmath/operations/BigInt.kt | 2 ++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index f7c021998..645e60c29 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -250,21 +250,21 @@ public interface Ring : Group, RingOperations { * neutral operation for multiplication */ public val one: T +} - public fun T.pow(exponent: ULong): T = when { - this == zero && exponent > 0UL -> zero - this == one -> this - this == -one -> powWithoutOptimization(exponent % 2UL) - else -> powWithoutOptimization(exponent) - } +public fun Ring.pow(base: T, exponent: ULong): T = when { + this == zero && exponent > 0UL -> zero + this == one -> base + this == -one -> powWithoutOptimization(base, exponent % 2UL) + else -> powWithoutOptimization(base, exponent) +} - private fun T.powWithoutOptimization(exponent: ULong): T = when (exponent) { - 0UL -> one - 1UL -> this - else -> { - val pre = powWithoutOptimization(exponent shr 1).let { it * it } - if (exponent and 1UL == 0UL) pre else pre * this - } +private fun Ring.powWithoutOptimization(base: T, exponent: ULong): T = when (exponent) { + 0UL -> one + 1UL -> base + else -> { + val pre = powWithoutOptimization(base, exponent shr 1).let { it * it } + if (exponent and 1UL == 0UL) pre else pre * base } } diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index 780a4591d..88c952928 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -98,6 +98,8 @@ public class BigInt internal constructor( else -> BigInt(sign, multiplyMagnitudeByUInt(magnitude, other)) } + public fun pow(other: ULong): BigInt = BigIntField { pow(this@BigInt, other) } + public operator fun times(other: Int): BigInt = when { other > 0 -> this * kotlin.math.abs(other).toUInt() other != Int.MIN_VALUE -> -this * kotlin.math.abs(other).toUInt() From ded01251d1e94af4edf315c3d5ad70dce6b06ee0 Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Wed, 12 May 2021 11:05:42 +0300 Subject: [PATCH 09/14] Karatsuba enhancement --- .../kotlin/space/kscience/kmath/operations/BigInt.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index 88c952928..be73e274a 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -324,10 +324,8 @@ public class BigInt internal constructor( } internal fun multiplyMagnitudes(mag1: Magnitude, mag2: Magnitude): Magnitude = when { - mag1.size + mag2.size < KARATSUBA_THRESHOLD || mag1.isEmpty() || mag2.isEmpty() -> naiveMultiplyMagnitudes( - mag1, - mag2 - ) + mag1.size + mag2.size < KARATSUBA_THRESHOLD || mag1.isEmpty() || mag2.isEmpty() -> + naiveMultiplyMagnitudes(mag1, mag2) // TODO implement Fourier else -> karatsubaMultiplyMagnitudes(mag1, mag2) } @@ -361,7 +359,7 @@ public class BigInt internal constructor( val z0 = x0 * y0 val z2 = x1 * y1 - val z1 = (x0 + x1) * (y1 + y0) - z0 - z2 + val z1 = (x0 - x1) * (y1 - y0) + z0 + z2 return (z2.shl(2 * halfSize * BASE_SIZE) + z1.shl(halfSize * BASE_SIZE) + z0).magnitude } From 1e71f29d5e64a5c933630f892fab29cdfbd36e63 Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Wed, 12 May 2021 13:59:41 +0300 Subject: [PATCH 10/14] Large addition tests added --- .../space/kscience/kmath/benchmarks/BigIntBenchmark.kt | 10 ++++++++++ .../kotlin/space/kscience/kmath/operations/BigInt.kt | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt index 639eaacf3..d3262379f 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt @@ -32,6 +32,16 @@ internal class BigIntBenchmark { blackhole.consume(jvmNumber + jvmNumber + jvmNumber) } + @Benchmark + fun kmAddLarge(blackhole: Blackhole) = BigIntField { + blackhole.consume(largeKmNumber + largeKmNumber + largeKmNumber) + } + + @Benchmark + fun jvmAddLarge(blackhole: Blackhole) = JBigIntegerField { + blackhole.consume(largeJvmNumber + largeJvmNumber + largeJvmNumber) + } + @Benchmark fun kmMultiply(blackhole: Blackhole) = BigIntField { blackhole.consume(kmNumber * kmNumber * kmNumber) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index be73e274a..605dfd265 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -281,7 +281,7 @@ public class BigInt internal constructor( } result[i] = (res and BASE).toUInt() - carry = (res shr BASE_SIZE) + carry = res shr BASE_SIZE } result[resultLength - 1] = carry.toUInt() From 8443fac4ae4df746f26a1dd444be1ef194d7baa5 Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Wed, 12 May 2021 14:10:32 +0300 Subject: [PATCH 11/14] UInt can be used in exponent (in pow) now. --- .../kotlin/space/kscience/kmath/operations/Algebra.kt | 2 ++ .../kotlin/space/kscience/kmath/operations/BigInt.kt | 1 + .../space/kscience/kmath/operations/BigIntAlgebraTest.kt | 8 +++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index 645e60c29..a23dc427c 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -259,6 +259,8 @@ public fun Ring.pow(base: T, exponent: ULong): T = when { else -> powWithoutOptimization(base, exponent) } +public fun Ring.pow(base: T, exponent: UInt): T = pow(base, exponent.toULong()) + private fun Ring.powWithoutOptimization(base: T, exponent: ULong): T = when (exponent) { 0UL -> one 1UL -> base diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index 605dfd265..45161149c 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -99,6 +99,7 @@ public class BigInt internal constructor( } public fun pow(other: ULong): BigInt = BigIntField { pow(this@BigInt, other) } + public fun pow(other: UInt): BigInt = BigIntField { pow(this@BigInt, other) } public operator fun times(other: Int): BigInt = when { other > 0 -> this * kotlin.math.abs(other).toUInt() diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt index 90cd29758..abc88934c 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt @@ -26,12 +26,18 @@ internal class BigIntAlgebraTest { fun testKBigIntegerRingPow() { BigIntField { for (num in -5..5) - for (exponent in 0UL..10UL) + for (exponent in 0U..10U) { + assertEquals( + num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), + num.toBigInt().pow(exponent.toULong()), + "$num ^ $exponent" + ) assertEquals( num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), num.toBigInt().pow(exponent), "$num ^ $exponent" ) + } } } From 1e945389314cf8d3072f919f5c18afd9a4dcca4d Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Thu, 13 May 2021 13:11:20 +0300 Subject: [PATCH 12/14] Unstable API annotation added + inconsistent docs fix + unnecessary BigIntField usage removed --- .../kmath/benchmarks/BigIntBenchmark.kt | 5 +++- .../kscience/kmath/operations/Algebra.kt | 5 +++- .../space/kscience/kmath/operations/BigInt.kt | 3 ++ .../kmath/operations/OptionalOperations.kt | 2 +- .../kmath/operations/BigIntAlgebraTest.kt | 30 +++++++++---------- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt index d3262379f..21b097609 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt @@ -10,16 +10,19 @@ import kotlinx.benchmark.Blackhole import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.State +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.operations.* +import java.math.BigInteger +@UnstableKMathAPI @State(Scope.Benchmark) internal class BigIntBenchmark { val kmNumber = BigIntField.number(Int.MAX_VALUE) val jvmNumber = JBigIntegerField.number(Int.MAX_VALUE) val largeKmNumber = BigIntField { number(11).pow(100_000UL) } - val largeJvmNumber = JBigIntegerField { number(11).pow(100_000) } + val largeJvmNumber: BigInteger = JBigIntegerField { number(11).pow(100_000) } val bigExponent = 50_000 @Benchmark diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index a23dc427c..8881ae93c 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -6,6 +6,7 @@ package space.kscience.kmath.operations import space.kscience.kmath.misc.Symbol +import space.kscience.kmath.misc.UnstableKMathAPI /** * Stub for DSL the [Algebra] is. @@ -247,11 +248,12 @@ public interface RingOperations : GroupOperations { */ public interface Ring : Group, RingOperations { /** - * neutral operation for multiplication + * The neutral element of multiplication */ public val one: T } +@UnstableKMathAPI public fun Ring.pow(base: T, exponent: ULong): T = when { this == zero && exponent > 0UL -> zero this == one -> base @@ -259,6 +261,7 @@ public fun Ring.pow(base: T, exponent: ULong): T = when { else -> powWithoutOptimization(base, exponent) } +@UnstableKMathAPI public fun Ring.pow(base: T, exponent: UInt): T = pow(base, exponent.toULong()) private fun Ring.powWithoutOptimization(base: T, exponent: ULong): T = when (exponent) { diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index 45161149c..0dcbbe567 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -98,7 +98,10 @@ public class BigInt internal constructor( else -> BigInt(sign, multiplyMagnitudeByUInt(magnitude, other)) } + @UnstableKMathAPI public fun pow(other: ULong): BigInt = BigIntField { pow(this@BigInt, other) } + + @UnstableKMathAPI public fun pow(other: UInt): BigInt = BigIntField { pow(this@BigInt, other) } public operator fun times(other: Int): BigInt = when { diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt index 8e3e6c777..7045c874e 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/OptionalOperations.kt @@ -147,7 +147,7 @@ public interface PowerOperations : Algebra { } /** - * Raises this element to the power [pow]. + * Raises this element to the power [power]. * * @receiver the base. * @param power the exponent. diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt index abc88934c..d3fca4351 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt @@ -5,6 +5,7 @@ package space.kscience.kmath.operations +import space.kscience.kmath.misc.UnstableKMathAPI import space.kscience.kmath.testutils.RingVerifier import kotlin.math.pow import kotlin.test.Test @@ -22,23 +23,22 @@ internal class BigIntAlgebraTest { assertEquals(res, 1_000_000.toBigInt()) } + @UnstableKMathAPI @Test fun testKBigIntegerRingPow() { - BigIntField { - for (num in -5..5) - for (exponent in 0U..10U) { - assertEquals( - num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), - num.toBigInt().pow(exponent.toULong()), - "$num ^ $exponent" - ) - assertEquals( - num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), - num.toBigInt().pow(exponent), - "$num ^ $exponent" - ) - } - } + for (num in -5..5) + for (exponent in 0U..10U) { + assertEquals( + num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), + num.toBigInt().pow(exponent.toULong()), + "$num ^ $exponent" + ) + assertEquals( + num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), + num.toBigInt().pow(exponent), + "$num ^ $exponent" + ) + } } @Test From c2047474016f8c7de92b83f741c7bb00630cc1a9 Mon Sep 17 00:00:00 2001 From: zhelenskiy Date: Thu, 13 May 2021 22:44:33 +0300 Subject: [PATCH 13/14] Reusing of existing power function --- .../kmath/benchmarks/BigIntBenchmark.kt | 4 +- .../kscience/kmath/operations/Algebra.kt | 20 ---------- .../space/kscience/kmath/operations/BigInt.kt | 6 +-- .../kmath/operations/algebraExtensions.kt | 39 ++++++++++++------- .../kmath/operations/BigIntAlgebraTest.kt | 8 +--- .../kmath/operations/DoubleFieldTest.kt | 12 ++++++ 6 files changed, 41 insertions(+), 48 deletions(-) diff --git a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt index 21b097609..749cd5e75 100644 --- a/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt +++ b/benchmarks/src/jvmMain/kotlin/space/kscience/kmath/benchmarks/BigIntBenchmark.kt @@ -21,7 +21,7 @@ internal class BigIntBenchmark { val kmNumber = BigIntField.number(Int.MAX_VALUE) val jvmNumber = JBigIntegerField.number(Int.MAX_VALUE) - val largeKmNumber = BigIntField { number(11).pow(100_000UL) } + val largeKmNumber = BigIntField { number(11).pow(100_000U) } val largeJvmNumber: BigInteger = JBigIntegerField { number(11).pow(100_000) } val bigExponent = 50_000 @@ -67,7 +67,7 @@ internal class BigIntBenchmark { @Benchmark fun kmPower(blackhole: Blackhole) = BigIntField { - blackhole.consume(kmNumber.pow(bigExponent.toULong())) + blackhole.consume(kmNumber.pow(bigExponent.toUInt())) } @Benchmark diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt index 8881ae93c..b444ed964 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/Algebra.kt @@ -253,26 +253,6 @@ public interface Ring : Group, RingOperations { public val one: T } -@UnstableKMathAPI -public fun Ring.pow(base: T, exponent: ULong): T = when { - this == zero && exponent > 0UL -> zero - this == one -> base - this == -one -> powWithoutOptimization(base, exponent % 2UL) - else -> powWithoutOptimization(base, exponent) -} - -@UnstableKMathAPI -public fun Ring.pow(base: T, exponent: UInt): T = pow(base, exponent.toULong()) - -private fun Ring.powWithoutOptimization(base: T, exponent: ULong): T = when (exponent) { - 0UL -> one - 1UL -> base - else -> { - val pre = powWithoutOptimization(base, exponent shr 1).let { it * it } - if (exponent and 1UL == 0UL) pre else pre * base - } -} - /** * Represents field without without multiplicative and additive identities, i.e. algebraic structure with associative, binary, commutative operations * [add] and [multiply]; binary operation [divide] as multiplication of left operand by reciprocal of right one. diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt index 0dcbbe567..924ef07f4 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt @@ -98,11 +98,7 @@ public class BigInt internal constructor( else -> BigInt(sign, multiplyMagnitudeByUInt(magnitude, other)) } - @UnstableKMathAPI - public fun pow(other: ULong): BigInt = BigIntField { pow(this@BigInt, other) } - - @UnstableKMathAPI - public fun pow(other: UInt): BigInt = BigIntField { pow(this@BigInt, other) } + public fun pow(exponent: UInt): BigInt = BigIntField.power(this@BigInt, exponent) public operator fun times(other: Int): BigInt = when { other > 0 -> this * kotlin.math.abs(other).toUInt() diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt index b8670553d..9691c9d17 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt @@ -97,34 +97,45 @@ public fun Sequence.averageWith(space: S): T where S : Ring, S : Sc //TODO optimized power operation /** - * Raises [arg] to the natural power [power]. + * Raises [arg] to the non-negative integer power [power]. + * + * Special case: 0 ^ 0 is 1. * * @receiver the algebra to provide multiplication. * @param arg the base. * @param power the exponent. * @return the base raised to the power. + * @author Evgeniy Zhelenskiy */ -public fun Ring.power(arg: T, power: Int): T { - require(power >= 0) { "The power can't be negative." } - require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." } - if (power == 0) return one - var res = arg - repeat(power - 1) { res *= arg } - return res +public fun Ring.power(arg: T, power: UInt): T = when { + this == zero && power > 0U -> zero + this == one -> arg + this == -one -> powWithoutOptimization(arg, power % 2U) + else -> powWithoutOptimization(arg, power) } +private fun Ring.powWithoutOptimization(base: T, exponent: UInt): T = when (exponent) { + 0U -> one + 1U -> base + else -> { + val pre = powWithoutOptimization(base, exponent shr 1).let { it * it } + if (exponent and 1U == 0U) pre else pre * base + } +} + + /** * Raises [arg] to the integer power [power]. * + * Special case: 0 ^ 0 is 1. + * * @receiver the algebra to provide multiplication and division. * @param arg the base. * @param power the exponent. * @return the base raised to the power. - * @author Iaroslav Postovalov + * @author Iaroslav Postovalov, Evgeniy Zhelenskiy */ -public fun Field.power(arg: T, power: Int): T { - require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." } - if (power == 0) return one - if (power < 0) return one / (this as Ring).power(arg, -power) - return (this as Ring).power(arg, power) +public fun Field.power(arg: T, power: Int): T = when { + power < 0 -> one / (this as Ring).power(arg, if (power == Int.MIN_VALUE) Int.MAX_VALUE.toUInt().inc() else (-power).toUInt()) + else -> (this as Ring).power(arg, power.toUInt()) } diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt index d3fca4351..0527f5252 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigIntAlgebraTest.kt @@ -27,18 +27,12 @@ internal class BigIntAlgebraTest { @Test fun testKBigIntegerRingPow() { for (num in -5..5) - for (exponent in 0U..10U) { - assertEquals( - num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), - num.toBigInt().pow(exponent.toULong()), - "$num ^ $exponent" - ) + for (exponent in 0U..10U) assertEquals( num.toDouble().pow(exponent.toInt()).toLong().toBigInt(), num.toBigInt().pow(exponent), "$num ^ $exponent" ) - } } @Test diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt index c482dc978..7e689d079 100644 --- a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/DoubleFieldTest.kt @@ -18,4 +18,16 @@ internal class DoubleFieldTest { val sqrt = DoubleField { sqrt(25 * one) } assertEquals(5.0, sqrt) } + + @Test + fun testPow() = DoubleField { + val num = 5 * one + assertEquals(5.0, power(num, 1)) + assertEquals(25.0, power(num, 2)) + assertEquals(1.0, power(num, 0)) + assertEquals(0.2, power(num, -1)) + assertEquals(0.04, power(num, -2)) + assertEquals(0.0, power(num, Int.MIN_VALUE)) + assertEquals(1.0, power(zero, 0)) + } } From bdb9ce6a59bf34b4ff21945f42a05c070752fcf7 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 14 May 2021 09:10:05 +0300 Subject: [PATCH 14/14] fixes to power --- .../kscience/kmath/operations/algebraExtensions.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt index 9691c9d17..338dc6a5c 100644 --- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt @@ -108,9 +108,9 @@ public fun Sequence.averageWith(space: S): T where S : Ring, S : Sc * @author Evgeniy Zhelenskiy */ public fun Ring.power(arg: T, power: UInt): T = when { - this == zero && power > 0U -> zero - this == one -> arg - this == -one -> powWithoutOptimization(arg, power % 2U) + arg == zero && power > 0U -> zero + arg == one -> arg + arg == -one -> powWithoutOptimization(arg, power % 2U) else -> powWithoutOptimization(arg, power) } @@ -135,7 +135,7 @@ private fun Ring.powWithoutOptimization(base: T, exponent: UInt): T = whe * @return the base raised to the power. * @author Iaroslav Postovalov, Evgeniy Zhelenskiy */ -public fun Field.power(arg: T, power: Int): T = when { - power < 0 -> one / (this as Ring).power(arg, if (power == Int.MIN_VALUE) Int.MAX_VALUE.toUInt().inc() else (-power).toUInt()) - else -> (this as Ring).power(arg, power.toUInt()) +public fun Field.power(arg: T, power: UInt): T = when { + power < 0 -> one / (this as Ring).power(arg, power) + else -> (this as Ring).power(arg, power) }