From 45eca71b3a9f914f36b8e0ccaafb88659b8ab5e9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 12 Aug 2023 16:36:09 +0300 Subject: [PATCH] Add BigDecimal implementation --- .../kscience/kmath/operations/BigDecimal.kt | 94 +++++++++++++++++++ .../space/kscience/kmath/operations/BigInt.kt | 4 +- .../kmath/operations/BigDecimalTest.kt | 19 ++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigDecimal.kt create mode 100644 kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigDecimalTest.kt diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigDecimal.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigDecimal.kt new file mode 100644 index 000000000..0f9aeec96 --- /dev/null +++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigDecimal.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.operations + +import space.kscience.kmath.UnstableKMathAPI +import kotlin.math.abs +import kotlin.math.min + +/** + * Decimal value with unfixed length + * + * The value is computed as `intValue*10^scale`. + */ +@UnstableKMathAPI +public data class BigDecimal(val intValue: BigInt, val scale: Int) + +/** + * Convert current [BigInt] to a [BigDecimal] with scale 0. + */ +@UnstableKMathAPI +public fun BigInt.asDecimal(): BigDecimal = BigDecimal(this, 0) + +/** + * A field for [BigDecimal] on top of [BigIntField] + */ +@UnstableKMathAPI +public object BigDecimalField : Field { + + override val one: BigDecimal = BigIntField.one.asDecimal() + override val zero: BigDecimal = BigIntField.zero.asDecimal() + + internal val ten: BigInt = BigIntField.number(10) + + internal fun pow10(power: UInt): BigInt = ten.pow(power) + + public fun Number.toDecimal(): BigDecimal = number(this) + + /** + * Rescale a [BigDecimal] to have [targetScale]. If the target scale is larger than the current scale, loss of precision is possible. + */ + public fun BigDecimal.rescale(targetScale: Int): BigDecimal = if (targetScale == scale) { + this + } else { + val scaleDif = scale - targetScale + if (scaleDif > 0) { + BigDecimal(BigIntField.multiply(intValue, pow10(scaleDif.toUInt())), targetScale) + } else { + BigDecimal(BigIntField.divide(intValue, pow10(abs(scaleDif).toUInt())), targetScale) + } + } + + public fun BigDecimal.pow10(power: Int): BigDecimal = BigDecimal(intValue, scale + power) + + override fun add(left: BigDecimal, right: BigDecimal): BigDecimal = if (left.scale == right.scale) { + BigDecimal(BigIntField.add(left.intValue, right.intValue), left.scale) + } else { + val minScale = min(left.scale, right.scale) + //rescale both to a minimal scale + add(left.rescale(minScale), right.rescale(minScale)) + } + + override fun BigDecimal.unaryMinus(): BigDecimal = BigDecimal(-intValue, scale) + + override fun scale(a: BigDecimal, value: Double): BigDecimal = with(BigIntField) { + BigDecimal(a.intValue * value, a.scale) + } + + override fun divide(left: BigDecimal, right: BigDecimal): BigDecimal = + BigDecimal(BigIntField.divide(left.intValue, right.intValue), left.scale - right.scale) + + override fun multiply(left: BigDecimal, right: BigDecimal): BigDecimal = + BigDecimal(BigIntField.multiply(left.intValue, right.intValue), left.scale + right.scale) + + + public operator fun Double.times(other: BigDecimal): BigDecimal = toDecimal() * other + + public operator fun Double.div(other: BigDecimal): BigDecimal = toDecimal() / other + + public operator fun Double.plus(other: BigDecimal): BigDecimal = toDecimal() + other + + public operator fun Double.minus(other: BigDecimal): BigDecimal = toDecimal() - other + + + public operator fun BigDecimal.times(other: Double): BigDecimal = this * other.toDecimal() + + public operator fun BigDecimal.div(other: Double): BigDecimal = this / other.toDecimal() + + public operator fun BigDecimal.plus(other: Double): BigDecimal = this + other.toDecimal() + + public operator fun BigDecimal.minus(other: Double): BigDecimal = this - other.toDecimal() +} \ No newline at end of file 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 34a6d4a80..dcf72b5f9 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 @@ -16,7 +16,6 @@ import kotlin.math.min import kotlin.math.sign private typealias Magnitude = UIntArray -private typealias TBase = ULong /** * Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger). @@ -215,6 +214,9 @@ public class BigInt internal constructor( } } + /** + * Convert this [BigInt] to a string using hexadecimal representation + */ override fun toString(): String { if (this.sign == 0.toByte()) { return "0x0" diff --git a/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigDecimalTest.kt b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigDecimalTest.kt new file mode 100644 index 000000000..ae22f63b2 --- /dev/null +++ b/kmath-core/src/commonTest/kotlin/space/kscience/kmath/operations/BigDecimalTest.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2018-2023 KMath contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package space.kscience.kmath.operations + +import space.kscience.kmath.UnstableKMathAPI +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(UnstableKMathAPI::class) +class BigDecimalTest { + + @Test + fun simpleExpression() = with(BigDecimalField) { + assertEquals( 1000.0.toDecimal(),22.2.toDecimal().pow10(2) / 1.11) + } +} \ No newline at end of file