Skip to content

Commit f025d2b

Browse files
authored
feat: Kotlin/Native implementation of BigDecimal and BigInteger (#1200)
1 parent 2653fbf commit f025d2b

File tree

12 files changed

+305
-86
lines changed

12 files changed

+305
-86
lines changed

gradle.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ kotlin.code.style=official
22
kotlin.incremental.js=true
33
kotlin.incremental.multiplatform=true
44
kotlin.mpp.stability.nowarn=true
5+
# kotlin.native.binary.sourceInfoType=libbacktrace
56
kotlin.native.ignoreDisabledTargets=true
67

78
# atomicfu
@@ -16,4 +17,4 @@ org.gradle.jvmargs=-Xmx2G -XX:MaxMetaspaceSize=1G
1617
sdkVersion=1.3.32-SNAPSHOT
1718

1819
# codegen
19-
codegenVersion=0.33.32-SNAPSHOT
20+
codegenVersion=0.33.32-SNAPSHOT

gradle/libs.versions.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ slf4j-version = "2.0.16"
1515
slf4j-v1x-version = "1.7.36"
1616
crt-kotlin-version = "0.8.10"
1717
micrometer-version = "1.13.6"
18+
kotlin-multiplatform-bignum-version = "0.3.10"
1819
kotlinx-datetime-version = "0.6.1"
1920

2021
# codegen
@@ -103,6 +104,8 @@ ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-ve
103104
kaml = { module = "com.charleskorn.kaml:kaml", version.ref = "kaml-version" }
104105
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup-version" }
105106

107+
kotlin-multiplatform-bignum = { module = "com.ionspin.kotlin:bignum", version.ref = "kotlin-multiplatform-bignum-version" }
108+
106109
[plugins]
107110
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka-version"}
108111
kotlin-jvm = {id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" }

runtime/build.gradle.kts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,27 @@ subprojects {
8888
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile> {
8989
compilerOptions {
9090
freeCompilerArgs.add("-Xexpect-actual-classes")
91+
92+
// FIXME When building LinuxX64 on AL2 the linker inclues a bunch of dynamic links to unavailable versions
93+
// of zlib. The below workaround forces the linker to statically link zlib but it's a hack because the
94+
// linker will still dynamically link zlib (although the executable will no longer fail at runtime due to
95+
// link resolution failures). The correct solution for this is probably containerized builds similar to
96+
// what we do in aws-crt-kotlin. The following compiler args were helpful in debugging this issue:
97+
// * Enable verbose compiler output : -verbose
98+
// * Increase verbosity during the compiler's linker phase : -Xverbose-phases=Linker
99+
// * Enable verbose linker output from gold : -linker-option --verbose
100+
if (target.contains("linux", ignoreCase = true)) {
101+
freeCompilerArgs.addAll(
102+
listOf(
103+
"-linker-option", // The subsequent argument is for the linker
104+
"-Bstatic", // Enable static linking for the libraries that follow
105+
"-linker-option", // The subsequent argument is for the linker
106+
"-lz", // Link zlib statically (because of -Bstatic above)
107+
"-linker-option", // The subsequent argument is for the linker
108+
"-Bdynamic", // Restore dynamic linking, which is the default
109+
),
110+
)
111+
}
91112
}
92113
}
93114
}

runtime/runtime-core/api/runtime-core.api

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ public final class aws/smithy/kotlin/runtime/config/EnvironmentSettingKt {
346346
public final class aws/smithy/kotlin/runtime/content/BigDecimal : java/lang/Number, java/lang/Comparable {
347347
public fun <init> (Laws/smithy/kotlin/runtime/content/BigInteger;I)V
348348
public fun <init> (Ljava/lang/String;)V
349+
public synthetic fun <init> (Ljava/math/BigDecimal;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
349350
public final fun byteValue ()B
350351
public fun compareTo (Laws/smithy/kotlin/runtime/content/BigDecimal;)I
351352
public synthetic fun compareTo (Ljava/lang/Object;)I
@@ -354,9 +355,11 @@ public final class aws/smithy/kotlin/runtime/content/BigDecimal : java/lang/Numb
354355
public final fun floatValue ()F
355356
public final fun getExponent ()I
356357
public final fun getMantissa ()Laws/smithy/kotlin/runtime/content/BigInteger;
357-
public final fun getValue ()Ljava/lang/String;
358+
public fun hashCode ()I
358359
public final fun intValue ()I
359360
public final fun longValue ()J
361+
public final fun minus (Laws/smithy/kotlin/runtime/content/BigDecimal;)Laws/smithy/kotlin/runtime/content/BigDecimal;
362+
public final fun plus (Laws/smithy/kotlin/runtime/content/BigDecimal;)Laws/smithy/kotlin/runtime/content/BigDecimal;
360363
public final fun shortValue ()S
361364
public fun toByte ()B
362365
public fun toDouble ()D
@@ -365,6 +368,7 @@ public final class aws/smithy/kotlin/runtime/content/BigDecimal : java/lang/Numb
365368
public fun toLong ()J
366369
public final fun toPlainString ()Ljava/lang/String;
367370
public fun toShort ()S
371+
public fun toString ()Ljava/lang/String;
368372
}
369373

370374
public final class aws/smithy/kotlin/runtime/content/BigInteger : java/lang/Number, java/lang/Comparable {
@@ -376,7 +380,6 @@ public final class aws/smithy/kotlin/runtime/content/BigInteger : java/lang/Numb
376380
public final fun doubleValue ()D
377381
public fun equals (Ljava/lang/Object;)Z
378382
public final fun floatValue ()F
379-
public final fun getValue ()Ljava/lang/String;
380383
public fun hashCode ()I
381384
public final fun intValue ()I
382385
public final fun longValue ()J
@@ -391,6 +394,8 @@ public final class aws/smithy/kotlin/runtime/content/BigInteger : java/lang/Numb
391394
public fun toLong ()J
392395
public fun toShort ()S
393396
public fun toString ()Ljava/lang/String;
397+
public final fun toString (I)Ljava/lang/String;
398+
public static synthetic fun toString$default (Laws/smithy/kotlin/runtime/content/BigInteger;IILjava/lang/Object;)Ljava/lang/String;
394399
}
395400

396401
public abstract class aws/smithy/kotlin/runtime/content/ByteStream {

runtime/runtime-core/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ kotlin {
4444
}
4545
}
4646

47+
nativeMain {
48+
dependencies {
49+
implementation(libs.kotlin.multiplatform.bignum)
50+
}
51+
}
52+
4753
all {
4854
languageSettings.optIn("aws.smithy.kotlin.runtime.InternalApi")
4955
}

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/content/BigDecimal.kt

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,94 @@ package aws.smithy.kotlin.runtime.content
1111
public expect class BigDecimal(value: String) :
1212
Number,
1313
Comparable<BigDecimal> {
14+
1415
/**
1516
* Create an instance of [BigDecimal] from a mantissa and exponent.
16-
* @param mantissa a [BigInteger] representing the mantissa of this big decimal
17-
* @param exponent an [Int] representing the exponent of this big decimal
17+
* @param mantissa a [BigInteger] representing the [significant digits](https://en.wikipedia.org/wiki/Significand)
18+
* of this decimal value
19+
* @param exponent an [Int] representing the exponent of this decimal value
1820
*/
1921
public constructor(mantissa: BigInteger, exponent: Int)
2022

2123
/**
22-
* The mantissa of this decimal number
24+
* The [significant digits](https://en.wikipedia.org/wiki/Significand) of this decimal value
2325
*/
2426
public val mantissa: BigInteger
2527

2628
/**
27-
* The exponent of this decimal number.
28-
* If zero or positive, this represents the number of digits to the right of the decimal point.
29-
* If negative, the mantissa is multiplied by ten to the power of the negation of the scale.
29+
* The exponent of this decimal number. If zero or positive, this represents the number of digits to the right of
30+
* the decimal point. If negative, the [mantissa] is multiplied by ten to the power of the negation of the scale.
3031
*/
3132
public val exponent: Int
3233

34+
/**
35+
* Converts this value to a [Byte], which may involve rounding or truncation
36+
*/
3337
override fun toByte(): Byte
38+
39+
/**
40+
* Converts this value to a [Double], which may involve rounding or truncation
41+
*/
3442
override fun toDouble(): Double
43+
44+
/**
45+
* Converts this value to a [Float], which may involve rounding or truncation
46+
*/
3547
override fun toFloat(): Float
48+
49+
/**
50+
* Converts this value to a [Short], which may involve rounding or truncation
51+
*/
3652
override fun toShort(): Short
53+
54+
/**
55+
* Converts this value to an [Int], which may involve rounding or truncation
56+
*/
3757
override fun toInt(): Int
58+
59+
/**
60+
* Converts this value to a [Long], which may involve rounding or truncation
61+
*/
3862
override fun toLong(): Long
63+
64+
/**
65+
* Returns the decimal (i.e., radix-10) string representation of this value in long-form (i.e., _not_ scientific)
66+
* notation
67+
*/
3968
public fun toPlainString(): String
69+
70+
/**
71+
* Returns the decimal (i.e., radix-10) string representation of this value using scientific notation if an exponent
72+
* is needed
73+
*/
74+
override fun toString(): String
75+
76+
/**
77+
* Returns a hash code for this value
78+
*/
79+
override fun hashCode(): Int
80+
81+
/**
82+
* Checks if this value is equal to the given object
83+
* @param other The other value to compare against
84+
*/
4085
override fun equals(other: Any?): Boolean
86+
87+
/**
88+
* Returns the sum of this value and the given value
89+
* @param other The other value to add (i.e., the addend)
90+
*/
91+
public operator fun plus(other: BigDecimal): BigDecimal
92+
93+
/**
94+
* Returns the difference of this value and the given value
95+
* @param other The value to subtract (i.e., the subtrahend)
96+
*/
97+
public operator fun minus(other: BigDecimal): BigDecimal
98+
99+
/**
100+
* Compare this value to the given value for in/equality
101+
* @param other The value to compare against
102+
*/
41103
public override operator fun compareTo(other: BigDecimal): Int
42104
}

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/content/BigInteger.kt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,86 @@ package aws.smithy.kotlin.runtime.content
1111
public expect class BigInteger(value: String) :
1212
Number,
1313
Comparable<BigInteger> {
14+
1415
/**
1516
* Create an instance of [BigInteger] from a [ByteArray]
1617
* @param bytes ByteArray representing the large integer
1718
*/
1819
public constructor(bytes: ByteArray)
1920

21+
/**
22+
* Converts this value to a [Byte], which may involve rounding or truncation
23+
*/
2024
override fun toByte(): Byte
25+
26+
/**
27+
* Converts this value to a [Long], which may involve rounding or truncation
28+
*/
2129
override fun toLong(): Long
30+
31+
/**
32+
* Converts this value to a [Short], which may involve rounding or truncation
33+
*/
2234
override fun toShort(): Short
35+
36+
/**
37+
* Converts this value to an [Int], which may involve rounding or truncation
38+
*/
2339
override fun toInt(): Int
40+
41+
/**
42+
* Converts this value to a [Float], which may involve rounding or truncation
43+
*/
2444
override fun toFloat(): Float
45+
46+
/**
47+
* Converts this value to a [Double], which may involve rounding or truncation
48+
*/
2549
override fun toDouble(): Double
50+
51+
/**
52+
* Returns the decimal (i.e., radix-10) string representation of this value
53+
*/
2654
override fun toString(): String
55+
56+
/**
57+
* Returns a string representation of this value in the given radix
58+
* @param radix The [numerical base](https://en.wikipedia.org/wiki/Radix) in which to represent the value
59+
*/
60+
public fun toString(radix: Int = 10): String
61+
62+
/**
63+
* Returns a hash code for this value
64+
*/
2765
override fun hashCode(): Int
66+
67+
/**
68+
* Checks if this value is equal to the given object
69+
* @param other The other value to compare against
70+
*/
2871
override fun equals(other: Any?): Boolean
72+
73+
/**
74+
* Returns the sum of this value and the given value
75+
* @param other The other value to add (i.e., the addend)
76+
*/
2977
public operator fun plus(other: BigInteger): BigInteger
78+
79+
/**
80+
* Returns the difference of this value and the given value
81+
* @param other The value to subtract (i.e., the subtrahend)
82+
*/
3083
public operator fun minus(other: BigInteger): BigInteger
84+
85+
/**
86+
* Returns the [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) binary representation of this
87+
* value
88+
*/
3189
public fun toByteArray(): ByteArray
90+
91+
/**
92+
* Compare this value to the given value for in/equality
93+
* @param other The value to compare against
94+
*/
3295
public override operator fun compareTo(other: BigInteger): Int
3396
}

runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/content/BigIntegerTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ class BigIntegerTest {
8484
"0x123456789abcdef0" to "1311768467463790320",
8585
"0x00ffffffffffffffffffffffffffffffec" to "340282366920938463463374607431768211436",
8686
"0x81445edf51ddc07216da5621c727bfd379d400f3da08018d45749a" to "-52134902384590238490284023839028330923830129830129301234239834982",
87-
8887
)
8988

9089
tests.forEach { (hex, expected) ->

runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/content/BigDecimalJVM.kt

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,52 @@
44
*/
55
package aws.smithy.kotlin.runtime.content
66

7-
public actual class BigDecimal actual constructor(public val value: String) :
7+
import java.math.BigDecimal as JvmBigDecimal
8+
9+
public actual class BigDecimal private constructor(private val delegate: JvmBigDecimal) :
810
Number(),
911
Comparable<BigDecimal> {
10-
private val delegate = java.math.BigDecimal(value)
1112

12-
public actual constructor(mantissa: BigInteger, exponent: Int) : this(
13-
java.math.BigDecimal(
14-
java.math.BigInteger(mantissa.toString()),
15-
exponent,
16-
).toPlainString(),
17-
)
13+
private companion object {
14+
/**
15+
* Returns a new or existing [BigDecimal] wrapper for the given delegate [value]
16+
* @param value The delegate value to wrap
17+
* @param left A candidate wrapper which may already contain [value]
18+
* @param right A candidate wrapper which may already contain [value]
19+
*/
20+
fun coalesceOrCreate(value: JvmBigDecimal, left: BigDecimal, right: BigDecimal): BigDecimal = when (value) {
21+
left.delegate -> left
22+
right.delegate -> right
23+
else -> BigDecimal(value)
24+
}
25+
}
26+
27+
public actual constructor(value: String) : this(JvmBigDecimal(value))
28+
public actual constructor(mantissa: BigInteger, exponent: Int) : this(JvmBigDecimal(mantissa.delegate, exponent))
1829

1930
public actual fun toPlainString(): String = delegate.toPlainString()
20-
actual override fun toByte(): Byte = delegate.toByte()
21-
actual override fun toDouble(): Double = delegate.toDouble()
22-
actual override fun toFloat(): Float = delegate.toFloat()
23-
actual override fun toInt(): Int = delegate.toInt()
24-
actual override fun toLong(): Long = delegate.toLong()
25-
actual override fun toShort(): Short = delegate.toShort()
31+
public actual override fun toString(): String = delegate.toString()
32+
public actual override fun toByte(): Byte = delegate.toByte()
33+
public actual override fun toDouble(): Double = delegate.toDouble()
34+
public actual override fun toFloat(): Float = delegate.toFloat()
35+
public actual override fun toInt(): Int = delegate.toInt()
36+
public actual override fun toLong(): Long = delegate.toLong()
37+
public actual override fun toShort(): Short = delegate.toShort()
2638

27-
actual override fun equals(other: Any?): Boolean = other is BigDecimal && delegate == other.delegate
39+
public actual override fun equals(other: Any?): Boolean = other is BigDecimal && delegate == other.delegate
40+
public actual override fun hashCode(): Int = 31 + delegate.hashCode()
2841

2942
public actual val mantissa: BigInteger
30-
get() = BigInteger(delegate.unscaledValue().toString())
43+
get() = BigInteger(delegate.unscaledValue())
3144

3245
public actual val exponent: Int
3346
get() = delegate.scale()
3447

48+
public actual operator fun plus(other: BigDecimal): BigDecimal =
49+
coalesceOrCreate(delegate + other.delegate, this, other)
50+
51+
public actual operator fun minus(other: BigDecimal): BigDecimal =
52+
coalesceOrCreate(delegate - other.delegate, this, other)
53+
3554
actual override fun compareTo(other: BigDecimal): Int = delegate.compareTo(other.delegate)
3655
}

0 commit comments

Comments
 (0)