DEV Community

Kotools
Kotools

Posted on • Originally published at github.com

⚡ Why Integer no longer stores its value as a String

If you've used the experimental Integer type from Kotools Types before 5.2.0, you may have noticed arithmetic getting slower as numbers grew larger. That wasn't your imagination — it came from how Integer stored its value internally.

🐛 The problem

Every Integer operation — +, -, *, comparisons, even toString() — had to work with a String representation of the number. That means every single operation reparsed the digits from scratch before it could do any arithmetic, and re-serialized them back to a String afterward:

// Simplified: what every operation used to pay for
val x = "99999999999999999999" // stored as a String
val y = "1"
// '+' had to parse both strings into a computable form,
// add them, then format the result back into a String.
Enter fullscreen mode Exit fullscreen mode

For a handful of small numbers this cost is invisible. For code that performs many operations on large integers, it adds up — and it's entirely avoidable, since the underlying value doesn't actually change shape between operations.

🤔 Why it happened

String was a convenient first representation: printing an Integer is free, and arbitrary precision comes for granted. But parsing and formatting are O(d) operations for a d-digit number. Doing that on every arithmetic call means the total cost of a chain of operations grows with both the number of operations and the number of digits. That's the case even though a proper big-integer representation only needs to pay the parsing/formatting cost once, at the boundary.

✅ The fix: a real arbitrary-precision representation per platform

Integer from Kotools Types 5.2.0 delegates to whatever native arbitrary-precision integer type each platform already provides — or, where none exists, to a purpose-built implementation:

Platform Representation
JVM java.lang.BigInteger
JavaScript BigInt
Native Custom sign-magnitude implementation

None of these store digits as text. Arithmetic operates directly on the underlying numeric representation, with parsing and formatting only happening at the actual boundaries — parse(), fromLong(), and toString() — not on every + or * in between.

@OptIn(ExperimentalKotoolsTypesApi::class)
fun main() {
    val x: Integer = Integer.fromLong(9223372036854775807)
    val y: Integer = Integer.fromLong(10)

    val sum: Integer = x + y
    check(sum == Integer.parse("9223372036854775817"))

    val product: Integer = x * y
    check(product == Integer.parse("92233720368547758070"))
}
Enter fullscreen mode Exit fullscreen mode

As a side benefit, dropping the previous approach also let the library remove its only third-party runtime dependency on Kotlin/Native — one less dependency to audit and update.

Integer is annotated with @ExperimentalKotoolsTypesApi and will be stabilized in a future release.

🛠️ Adding Kotools Types to your project

See the Installation section of the project's README for Gradle/Maven setup — this article was written against version 5.2.0.

This API is @ExperimentalKotoolsTypesApi, so opt in either per call-site with @OptIn(ExperimentalKotoolsTypesApi::class) or project-wide via the compiler argument:

// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-opt-in=org.kotools.types.ExperimentalKotoolsTypesApi")
    }
}
Enter fullscreen mode Exit fullscreen mode

See also: GitHub, API reference

Read the 5.2.0 highlights roundup for the other changes shipped in this release.

💬 Discussion

Have you hit performance issues from string-based arbitrary-precision arithmetic in other libraries? How did you work around it?

Top comments (0)