kotlin-multiplatform-bignum

repository·main·Indexed 19 days ago

https://github.com/ionspin/kotlin-multiplatform-bignum

A pure Kotlin implementation of arbitrary precision arithmetic (BigNum) for integers and floating-point numbers, designed for Kotlin Multiplatform (KMP). It provides BigInteger and BigDecimal types with support for basic arithmetic, bitwise operations, modular arithmetic via ModularBigInteger, and configurable precision and rounding through DecimalMode. Additionally, it offers serialization support for KotlinX Serialization with both array-based and human-readable strategies.

Tokens
4.2K
Snippets
13
Records
18
Agent score
17%

What's inside kotlin-multiplatform-bignum

  1. Work with Modular BigIntegers

    main

    You can perform modular arithmetic using ModularBigInteger. To convert a BigInteger to a ModularBigInteger, use the toModularBigInteger(modulo) extension function. Once converted, you can use the inverse() method to find the modular multiplicative inverse.

    val a = 100_002.toBigInteger()
    val modularA = a.toModularBigInteger(500.toBigInteger())
    
    // Get the modular inverse
    val inverse = modularA.inverse()
    
    // Print with modulo info
    println(modularA.toStringWithModulo())
  2. Configure arithmetic precision and rounding with DecimalMode

    main

    The DecimalMode data class controls the precision and rounding behavior of BigDecimal operations. It acts as a counterpart to Java's MathContext and scale simultaneously.

    Resolution Rules

    • A DecimalMode passed directly to an operation overrides any DecimalMode set on the BigDecimal operands.
    • If a DecimalMode is set during BigDecimal creation, it is used for all subsequent operations.
    • If two BigDecimals have different RoundingModes, an ArithmeticException is thrown.
    • If they have the same RoundingMode but different decimalPrecision, the larger precision is used.

    Infinite Precision

    Setting decimalPrecision to 0 and roundingMode to RoundingMode.NONE attempts to provide infinite precision. Note that division and exponentiation with negative parameters will use a default precision (the sum of operand precisions, or 6 if the sum is below 6). If a result cannot fit within the specified precision and RoundingMode.NONE is used, an ArithmeticException is thrown.

    data class DecimalMode(
        val decimalPrecision: Long = 0, 
        val roundingMode: RoundingMode = RoundingMode.NONE, 
        val scale: Long = -1
    )
  3. Choose between Array-based and Human-readable serializers

    main

    The library provides two distinct serialization strategies via SerializersModule. You must choose the one that fits your performance or readability requirements.

    1. Array-based serializers (arrayBasedSerializerModule): These are faster because they avoid the overhead of converting numbers to and from strings. They represent the number's internal structure (magnitude and sign) as arrays.
    2. Human-readable serializers (humanReadableSerializerModule): These represent the numbers as standard string representations (e.g., "12345"), making the output easy for humans to read and compatible with standard JSON number formats.
  4. Manage scale in BigDecimal operations

    main

    Scale refers to the number of digits to the right of the decimal point. The default is no scale (unlimited digits).

    Scale Rules

    • Unlimited Precision + No Scale: If both operands have unlimited precision and no scale, the result has unlimited precision and no scale.
    • Mixed Operands: If one operand is unlimited precision and the other is scaled, the result is unlimited precision.
    • Both Scaled: If both operands have a specified scale, the result scale is determined as follows:
      • Addition/Subtraction: max(scale1, scale2)
      • Multiplication: scale1 + scale2
      • Division: min(scale1, scale2)

    Note: When a scale is specified, a RoundingMode other than RoundingMode.NONE is required.

  5. Integrate bignum-serialization-kotlinx

    main

    To use BigInteger and BigDecimal with the KotlinX Serialization library, add the serialization support dependency to your Gradle configuration.

    Note on Platform Support: Due to KotlinX Serialization limitations, this library does not support linux arm or MinGW x86 targets. Additionally, due to a known build bug, only the JS IR variant is provided for the serialization support library.

    // Standard release
    implementation("com.ionspin.kotlin:bignum-serialization-kotlinx:0.3.2")
    
    // For snapshot builds
    repositories {
        maven {
            url = uri("https://oss.sonatype.org/content/repositories/snapshots")
        }
    }
    implementation("com.ionspin.kotlin:bignum:0.3.3-SNAPSHOT")
  6. Install the BigNum library via Gradle

    main

    To use the BigNum library in your Kotlin Multiplatform project, add the dependency to your build.gradle.kts file. You can use the stable version from Maven Central or a snapshot build for testing.

    // Stable version
    implementation("com.ionspin.kotlin:bignum:0.3.10")
    
    // Snapshot builds
    repositories {
        maven {
            url = uri("https://oss.sonatype.org/content/repositories/snapshots")
        }
    }
    implementation("com.ionspin.kotlin:bignum:0.3.11-SNAPSHOT")
  7. WASM Platform Limitations

    main

    The WASM platform is currently experimental. A known behavior is that BigDecimal.fromFloat() returns a value converted to an IEEE-754 number, which may result in precision differences compared to JVM, JS, or Native platforms.

    // On WASM, this might not be exact due to IEEE-754 conversion
    val a = BigDecimal.fromFloat(0.000000000000123f)
    // Expected: 1.2299999885799495E-13
  8. BigInteger Serialization Samples

    main

    Array-based (Fast)

    Uses bigIntegerArraySerializerModule. The output contains the magnitude as an array of integers and the sign.

    {"a":{"magnitude":[2083438008362598403,3369964156491764979,4367533269890700295,1274],"sign":"NEGATIVE"}}

    Human-readable

    Uses bigIntegerhumanReadableSerializerModule. The output is a simple string.

    {"a":"-1000000000000000000000000000002000000000000000000000000000003"}
  9. Serialize and Deserialize BigInteger and BigDecimal

    main

    To use these types with kotlinx.serialization, mark the fields as @Contextual in your @Serializable data classes and configure your Json instance with the appropriate serializersModule.

    val json = Json {
        serializersModule = arrayBasedSerializerModule // or humanReadableSerializerModule
    }
    
    @Serializable
    data class SomeDataHolder(
        @Contextual val bigInteger: BigInteger, 
        @Contextual val bigDecimal: BigDecimal
    )
    
    // Usage
    val bigInt = BigInteger.parseString("12345678901234567890")
    val bigDecimal = BigDecimal.parseString("1.234E-200")
    val someData = SomeDataHolder(bigInt, bigDecimal)
    
    val serialized = json.encodeToString(someData)
    val deserialized = json.decodeFromString<SomeDataHolder>(serialized)
  10. BigDecimal Serialization Samples

    main

    Human-readable

    Uses bigDecimalHumanReadableSerializerModule. The output is a standard decimal string.

    {"a":"1.000000000020000000000300000000004"}

    Array-based (Fast)

    Uses bigDecimalArraySerializerModule. The output contains the significand (magnitude and sign) and the exponent.

    {"a":{"significand":{"magnitude":[7819074433982969860,108420217250718],"sign":"POSITIVE"},"exponent":0}}
  11. Round BigDecimal to specific digit positions

    main

    The BigDecimal class provides two convenience methods for explicit rounding:

    1. roundToDigitPositionAfterDecimalPoint(digitPosition: Long, roundingMode: RoundingMode): Rounds to a specific position relative to the decimal point.
    2. roundToDigitPosition(digitPosition: Long, roundingMode: RoundingMode): Rounds to a specific digit precision regardless of the decimal point position.

    Examples

    // Rounding to 3 positions after the decimal point
    val rounded = BigDecimal.fromIntWithExponent(123456789, 3)
        .roundToDigitPositionAfterDecimalPoint(3, RoundingMode.CEILING)
    // Result: "1234.568"
    
    // Rounding to 3 digits of precision (regardless of decimal point)
    val rounded2 = BigDecimal.parseString("1234.5678")
        .roundToDigitPosition(3, RoundingMode.ROUND_HALF_TOWARDS_ZERO)
    // Result: "1230"
    
    // Rounding to 4 digits of precision
    val rounded3 = BigDecimal.parseString("0.0012345678")
        .roundToDigitPosition(4, RoundingMode.ROUND_HALF_TOWARDS_ZERO)
    // Result: "0.001"
    // Example of rounding to a specific position after decimal
    val rounded = BigDecimal.fromIntWithExponent(123456789, 3)
        .roundToDigitPositionAfterDecimalPoint(3, RoundingMode.CEILING)
    
    // Example of rounding to a specific digit precision
    val rounded2 = BigDecimal.parseString("1234.5678")
        .roundToDigitPosition(3, RoundingMode.ROUND_HALF_TOWARDS_ZERO)
  12. Perform Basic Arithmetic with BigInteger

    main

    The library supports standard arithmetic operators for BigInteger including addition (+), subtraction (-), multiplication (*), and division (/, %). For division that returns both quotient and remainder, use the divrem function.

    val a = BigInteger.fromLong(Long.MAX_VALUE)
    val b = BigInteger.fromInt(Int.MAX_VALUE)
    
    // Addition
    val sum = a + b
    
    // Subtraction
    val difference = a - b
    
    // Multiplication
    val product = a * b
    
    // Division (Quotient)
    val quotient = a / b
    
    // Division (Remainder)
    val remainder = a % b
    
    // Division (Quotient and Remainder)
    val result = a.divrem(b)
    println("Quotient: ${result.quotient}, Remainder: ${result.remainder}")