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()