Skip to content

Commit

Permalink
Decimal192 string parsing and representing in text fields (#134)
Browse files Browse the repository at this point in the history
* Create extensions for Decimal192 string parsing and representing in text fields

* Escape regex chars

* Add large numbers tests

* Fix cases for input
  • Loading branch information
micbakos-rdx authored May 10, 2024
1 parent 39ad077 commit 08ad89d
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 39 deletions.
1 change: 1 addition & 0 deletions jvm/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ androidx-compose-ui-preview = { module = "androidx.compose.ui:ui-tooling-preview
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
material = { module = "com.google.android.material:material", version.ref = "material" }
junit = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }

[plugins]
Expand Down
1 change: 1 addition & 0 deletions jvm/sargon-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp-coroutines")

testImplementation(libs.junit)
testImplementation(libs.junit.params)
testImplementation(libs.mockk)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testDebugRuntimeOnly(project(":sargon-desktop-debug"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.radixdlt.sargon.extensions

import com.radixdlt.sargon.CommonException
import com.radixdlt.sargon.Decimal192
import com.radixdlt.sargon.LocaleConfig
Expand Down Expand Up @@ -80,21 +81,51 @@ fun UInt.toDecimal192() = newDecimalFromU32(value = this)
val Decimal192.Companion.MAX_DIVISIBILITY: UByte
get() = 18u.toUByte()

fun Decimal192.Companion.init(
formattedString: String,
/**
* Parses a string as decimal value
* - only digits allowed
* - and decimal separator character
*
* Useful when converting a string from a text field to decimal 192
*/
@KoverIgnore
fun Decimal192.Companion.parseFromTextField(
textFieldString: String,
decimalFormat: DecimalFormatSymbols = DecimalFormatSymbols.getInstance()
): Decimal192 {
val config = LocaleConfig(
decimalSeparator = decimalFormat.decimalSeparator.toString(),
groupingSeparator = decimalFormat.groupingSeparator.toString()
): TextInputDecimal {
val charactersToReplace = "[^0-9\\\\${decimalFormat.decimalSeparator}]".toRegex()
val sanitizedString = textFieldString.replace(charactersToReplace, "")

return TextInputDecimal(
input = sanitizedString,
decimalSeparator = decimalFormat.decimalSeparator
)
}

return newDecimalFromFormattedString(
formattedString = formattedString,
locale = config
/**
* A pair of an input represented with a decimal
*/
class TextInputDecimal(
val input: String,
decimalSeparator: Char
) {
val decimal: Decimal192 = Decimal192.init(
formattedString = input,
config = LocaleConfig(
decimalSeparator = decimalSeparator.toString(),
groupingSeparator = null // We do not allow grouping separator characters in input
)
)
}

fun Decimal192.Companion.init(
formattedString: String,
config: LocaleConfig
): Decimal192 = newDecimalFromFormattedString(
formattedString = formattedString,
locale = config
)

/**
* The maximum possible value of [Decimal192], being:
* `3138550867693340381917894711603833208051.177722232017256447`
Expand Down Expand Up @@ -244,6 +275,22 @@ fun Decimal192.formattedPlain(
useGroupingSeparator = useGroupingSeparator
)

/**
* A human readable version of the decimal value
* - stripped from grouping separator
* - no truncation
* - with only decimal separator available
*
* Useful when converting a decimal to string for text fields.
*/
@KoverIgnore
fun Decimal192.formattedTextField(
format: DecimalFormatSymbols = DecimalFormatSymbols.getInstance()
) = formattedPlain(
format = format,
useGroupingSeparator = false
)

inline fun <T> Iterable<T>.sumOf(selector: (T) -> Decimal192): Decimal192 {
var sum: Decimal192 = 0.toDecimal192()
for (element in this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import com.radixdlt.sargon.extensions.exponent
import com.radixdlt.sargon.extensions.floor
import com.radixdlt.sargon.extensions.formatted
import com.radixdlt.sargon.extensions.formattedPlain
import com.radixdlt.sargon.extensions.init
import com.radixdlt.sargon.extensions.formattedTextField
import com.radixdlt.sargon.extensions.isNegative
import com.radixdlt.sargon.extensions.isPositive
import com.radixdlt.sargon.extensions.negative
import com.radixdlt.sargon.extensions.orZero
import com.radixdlt.sargon.extensions.parseFromTextField
import com.radixdlt.sargon.extensions.plus
import com.radixdlt.sargon.extensions.rounded
import com.radixdlt.sargon.extensions.string
Expand All @@ -32,13 +33,14 @@ import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import java.text.DecimalFormat
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments.of
import org.junit.jupiter.params.provider.MethodSource
import java.text.DecimalFormatSymbols
import java.util.Locale
import kotlin.math.PI

class Decimal192Test : SampleTestable<Decimal192> {

Expand Down Expand Up @@ -150,32 +152,6 @@ class Decimal192Test : SampleTestable<Decimal192> {
}
}

@Test
fun testStringParsing() {
val usFormat = DecimalFormat().apply {
decimalFormatSymbols = DecimalFormatSymbols(Locale.US)
}
val greekFormat = DecimalFormat().apply {
decimalFormatSymbols = DecimalFormatSymbols(Locale.forLanguageTag("el"))
}

assertEquals(
Decimal192.init(usFormat.format(PI), DecimalFormatSymbols(Locale.US)),
Decimal192.init(
greekFormat.format(PI),
DecimalFormatSymbols(Locale.forLanguageTag("el"))
)
)

assertThrows<CommonException.DecimalException> {
greekFormat.format(PI).toDecimal192()
}

assertDoesNotThrow {
Decimal192.init(DecimalFormat().format(PI))
}
}

@Test
fun testFromDouble() {
assertEquals("0.1", 0.1.toDecimal192().string)
Expand Down Expand Up @@ -234,4 +210,63 @@ class Decimal192Test : SampleTestable<Decimal192> {
format = DecimalFormatSymbols.getInstance(Locale.US),
useGroupingSeparator = false
)

@Nested
inner class UserInputTest {

@ParameterizedTest
@MethodSource("com.radixdlt.sargon.Decimal192Test#input")
fun test(
input: String,
decimal: Char,
grouping: Char,
formattedTextField: String,
sanitizedInput: String
) {
val decimalFormatSymbols = mockk<DecimalFormatSymbols>(relaxed = true).apply {
every { decimalSeparator } returns decimal
every { groupingSeparator } returns grouping
}

val result = Decimal192.parseFromTextField(
textFieldString = input,
decimalFormat = decimalFormatSymbols
)

assertEquals(formattedTextField, result.decimal.formattedTextField(
format = decimalFormatSymbols
))

assertEquals(sanitizedInput, result.input)
}

}

companion object {
@JvmStatic
fun input() = listOf(
// of(input, "<decimal>", "<grouping>, "formattedTextField", "sanitizedInput")
of("1234.9", ',', ' ', "12349", "12349"), // Wrong decimal separator is ignored
of("1 234,9", ',', ' ', "1234,9", "1234,9"), // Grouping separator is ignored
of("1234,9", ',', ' ', "1234,9", "1234,9"), // Correct format returns the same result
of("1234,999999999", ',', ' ', "1234,999999999", "1234,999999999"), // Correct format with many digits
of(",9", ',', ' ', "0,9", ",9"), // Without 0 at the beginning, adds zero in output

// Same with dot as separator
of("1234,9", '.', ',', "12349", "12349"),
of("1 234.9", '.', ',', "1234.9", "1234.9"),
of("1234.9", '.', ',', "1234.9", "1234.9"),
of("1234.999999999", '.', ',', "1234.999999999", "1234.999999999"),
of(".9", '.', ',', "0.9", ".9"),

of("0-9", '-', ' ', "0-9", "0-9"), // Decimal separator that needs to be escaped in regex
of("0^9", '^', ' ', "0^9", "0^9"), // Decimal separator that needs to be escaped in regex

of(" ", ',', ' ', "0", ""), // Blank resolves to 0 prints empty
of(" ", ' ', ',', "0", " "), // Blank with space as decimal separator resolves to 0 prints space

of("1,000,000.10", '.', ',', "1000000.1", "1000000.10"), // Large number resolves to decimal without trailing zero, prints the same number with 0
of("1.000.000,10", ',', '.', "1000000,1", "1000000,10")
)
}
}

0 comments on commit 08ad89d

Please sign in to comment.