Duration: do not use scientific format for large values
The largest duration value formatted in ns with maximal decimals would fit in 40 chars.
This commit is contained in:
@@ -108,15 +108,10 @@ OBJ_GETTER(Kotlin_Long_toStringRadix, KLong value, KInt radix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OBJ_GETTER(Kotlin_DurationValue_formatToExactDecimals, KDouble value, KInt decimals) {
|
OBJ_GETTER(Kotlin_DurationValue_formatToExactDecimals, KDouble value, KInt decimals) {
|
||||||
char cstring[32];
|
char cstring[40]; // log(2^62*1_000_000) + 2 (sign, decimal point) + 12 (max decimals)
|
||||||
konan::snprintf(cstring, sizeof(cstring), "%.*f", decimals, value);
|
konan::snprintf(cstring, sizeof(cstring), "%.*f", decimals, value);
|
||||||
RETURN_RESULT_OF(CreateStringFromCString, cstring)
|
RETURN_RESULT_OF(CreateStringFromCString, cstring)
|
||||||
}
|
}
|
||||||
|
|
||||||
OBJ_GETTER(Kotlin_DurationValue_formatScientificImpl, KDouble value) {
|
|
||||||
char cstring[16];
|
|
||||||
konan::snprintf(cstring, sizeof(cstring), "%.2e", value);
|
|
||||||
RETURN_RESULT_OF(CreateStringFromCString, cstring)
|
|
||||||
}
|
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -13,12 +13,3 @@ internal actual external fun formatToExactDecimals(value: Double, decimals: Int)
|
|||||||
internal actual fun formatUpToDecimals(value: Double, decimals: Int): String {
|
internal actual fun formatUpToDecimals(value: Double, decimals: Int): String {
|
||||||
return formatToExactDecimals(value, decimals).trimEnd('0')
|
return formatToExactDecimals(value, decimals).trimEnd('0')
|
||||||
}
|
}
|
||||||
|
|
||||||
@GCUnsafeCall("Kotlin_DurationValue_formatScientificImpl")
|
|
||||||
internal external fun formatScientificImpl(value: Double): String
|
|
||||||
|
|
||||||
internal actual fun formatScientific(value: Double): String {
|
|
||||||
val result = formatScientificImpl(value)
|
|
||||||
val expIndex = result.indexOf("e+0")
|
|
||||||
return if (expIndex < 0) result else result.removeRange(expIndex + 2, expIndex + 3)
|
|
||||||
}
|
|
||||||
@@ -15,13 +15,17 @@ internal actual fun formatToExactDecimals(value: Double, decimals: Int): String
|
|||||||
val pow = 10.0.pow(decimals)
|
val pow = 10.0.pow(decimals)
|
||||||
JsMath.round(abs(value) * pow) / pow * sign(value)
|
JsMath.round(abs(value) * pow) / pow * sign(value)
|
||||||
}
|
}
|
||||||
return rounded.asDynamic().toFixed(decimals).unsafeCast<String>()
|
return if (abs(rounded) < 1e21) {
|
||||||
|
// toFixed switches to scientific format after 1e21
|
||||||
|
rounded.asDynamic().toFixed(decimals).unsafeCast<String>()
|
||||||
|
} else {
|
||||||
|
// toPrecision outputs the specified number of digits, but only for positive numbers
|
||||||
|
val positive = abs(rounded)
|
||||||
|
val positiveString = positive.asDynamic().toPrecision(ceil(log10(positive)) + decimals).unsafeCast<String>()
|
||||||
|
if (rounded < 0) "-$positiveString" else positiveString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal actual fun formatUpToDecimals(value: Double, decimals: Int): String {
|
internal actual fun formatUpToDecimals(value: Double, decimals: Int): String {
|
||||||
return value.asDynamic().toLocaleString("en-us", json("maximumFractionDigits" to decimals)).unsafeCast<String>()
|
return value.asDynamic().toLocaleString("en-us", json("maximumFractionDigits" to decimals)).unsafeCast<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal actual fun formatScientific(value: Double): String {
|
|
||||||
return value.asDynamic().toExponential(2).unsafeCast<String>()
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -7,15 +7,11 @@ package kotlin.time
|
|||||||
|
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.concurrent.getOrSet
|
import kotlin.concurrent.getOrSet
|
||||||
|
|
||||||
private val rootNegativeExpFormatSymbols = DecimalFormatSymbols(Locale.ROOT).apply { exponentSeparator = "e" }
|
|
||||||
private val rootPositiveExpFormatSymbols = DecimalFormatSymbols(Locale.ROOT).apply { exponentSeparator = "e+" }
|
|
||||||
private val precisionFormats = Array(4) { ThreadLocal<DecimalFormat>() }
|
private val precisionFormats = Array(4) { ThreadLocal<DecimalFormat>() }
|
||||||
|
|
||||||
private fun createFormatForDecimals(decimals: Int) = DecimalFormat("0", rootNegativeExpFormatSymbols).apply {
|
private fun createFormatForDecimals(decimals: Int) = DecimalFormat("0").apply {
|
||||||
if (decimals > 0) minimumFractionDigits = decimals
|
if (decimals > 0) minimumFractionDigits = decimals
|
||||||
roundingMode = RoundingMode.HALF_UP
|
roundingMode = RoundingMode.HALF_UP
|
||||||
}
|
}
|
||||||
@@ -32,11 +28,3 @@ internal actual fun formatUpToDecimals(value: Double, decimals: Int): String =
|
|||||||
createFormatForDecimals(0)
|
createFormatForDecimals(0)
|
||||||
.apply { maximumFractionDigits = decimals }
|
.apply { maximumFractionDigits = decimals }
|
||||||
.format(value)
|
.format(value)
|
||||||
|
|
||||||
private val scientificFormat = ThreadLocal<DecimalFormat>()
|
|
||||||
internal actual fun formatScientific(value: Double): String =
|
|
||||||
scientificFormat.getOrSet { DecimalFormat("0E0", rootNegativeExpFormatSymbols).apply { minimumFractionDigits = 2 } }
|
|
||||||
.apply {
|
|
||||||
decimalFormatSymbols = if (value >= 1 || value <= -1) rootPositiveExpFormatSymbols else rootNegativeExpFormatSymbols
|
|
||||||
}
|
|
||||||
.format(value)
|
|
||||||
@@ -783,7 +783,10 @@ public value class Duration internal constructor(private val rawValue: Long) : C
|
|||||||
* and formatted with the specified [decimals] number of digits after decimal point.
|
* and formatted with the specified [decimals] number of digits after decimal point.
|
||||||
*
|
*
|
||||||
* Special cases:
|
* Special cases:
|
||||||
* - the infinite duration is formatted as `"Infinity"` without unit
|
* - an infinite duration is formatted as `"Infinity"` or `"-Infinity"` without a unit.
|
||||||
|
*
|
||||||
|
* @param decimals the number of digits after decimal point to show. The value must be non-negative.
|
||||||
|
* No more than 12 decimals will be shown, even if a larger number is requested.
|
||||||
*
|
*
|
||||||
* @return the value of duration in the specified [unit] followed by that unit abbreviated name: `d`, `h`, `m`, `s`, `ms`, `us`, or `ns`.
|
* @return the value of duration in the specified [unit] followed by that unit abbreviated name: `d`, `h`, `m`, `s`, `ms`, `us`, or `ns`.
|
||||||
*
|
*
|
||||||
@@ -795,10 +798,7 @@ public value class Duration internal constructor(private val rawValue: Long) : C
|
|||||||
require(decimals >= 0) { "decimals must be not negative, but was $decimals" }
|
require(decimals >= 0) { "decimals must be not negative, but was $decimals" }
|
||||||
val number = toDouble(unit)
|
val number = toDouble(unit)
|
||||||
if (number.isInfinite()) return number.toString()
|
if (number.isInfinite()) return number.toString()
|
||||||
return when {
|
return formatToExactDecimals(number, decimals.coerceAtMost(12)) + unit.shortName()
|
||||||
abs(number) < 1e14 -> formatToExactDecimals(number, decimals.coerceAtMost(12))
|
|
||||||
else -> formatScientific(number)
|
|
||||||
} + unit.shortName()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1212,4 +1212,3 @@ private fun millisToNanos(millis: Long): Long = millis * NANOS_IN_MILLIS
|
|||||||
|
|
||||||
internal expect fun formatToExactDecimals(value: Double, decimals: Int): String
|
internal expect fun formatToExactDecimals(value: Double, decimals: Int): String
|
||||||
internal expect fun formatUpToDecimals(value: Double, decimals: Int): String
|
internal expect fun formatUpToDecimals(value: Double, decimals: Int): String
|
||||||
internal expect fun formatScientific(value: Double): String
|
|
||||||
@@ -9,6 +9,7 @@ package test.time
|
|||||||
|
|
||||||
import test.numbers.assertAlmostEquals
|
import test.numbers.assertAlmostEquals
|
||||||
import kotlin.math.nextDown
|
import kotlin.math.nextDown
|
||||||
|
import kotlin.math.pow
|
||||||
import kotlin.native.concurrent.SharedImmutable
|
import kotlin.native.concurrent.SharedImmutable
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
import kotlin.time.*
|
import kotlin.time.*
|
||||||
@@ -520,7 +521,7 @@ class DurationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun formatInUnits() {
|
fun parseAndFormatInUnits() {
|
||||||
var d = with(Duration) {
|
var d = with(Duration) {
|
||||||
days(1) + hours(15) + minutes(31) + seconds(45) +
|
days(1) + hours(15) + minutes(31) + seconds(45) +
|
||||||
milliseconds(678) + microseconds(920) + nanoseconds(516.34)
|
milliseconds(678) + microseconds(920) + nanoseconds(516.34)
|
||||||
@@ -529,6 +530,13 @@ class DurationTest {
|
|||||||
fun test(unit: DurationUnit, vararg representations: String) {
|
fun test(unit: DurationUnit, vararg representations: String) {
|
||||||
assertFails { d.toString(unit, -1) }
|
assertFails { d.toString(unit, -1) }
|
||||||
assertEquals(representations.toList(), representations.indices.map { d.toString(unit, it) })
|
assertEquals(representations.toList(), representations.indices.map { d.toString(unit, it) })
|
||||||
|
for ((decimals, string) in representations.withIndex()) {
|
||||||
|
val d1 = Duration.parse(string)
|
||||||
|
assertEquals(d1, Duration.parseOrNull(string))
|
||||||
|
if (!(d1 == d || (d1 - d).absoluteValue <= (0.5 * 10.0.pow(-decimals)).toDuration(unit))) {
|
||||||
|
fail("Parsed value $d1 (from $string) is too far from the real value $d")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test(DurationUnit.DAYS, "2d", "1.6d", "1.65d", "1.647d")
|
test(DurationUnit.DAYS, "2d", "1.6d", "1.65d", "1.647d")
|
||||||
@@ -553,7 +561,13 @@ class DurationTest {
|
|||||||
|
|
||||||
assertEquals("0.500000000000s", Duration.seconds(0.5).toString(DurationUnit.SECONDS, 100))
|
assertEquals("0.500000000000s", Duration.seconds(0.5).toString(DurationUnit.SECONDS, 100))
|
||||||
assertEquals("99999000000000.000000000000ns", Duration.seconds(99_999).toString(DurationUnit.NANOSECONDS, 15))
|
assertEquals("99999000000000.000000000000ns", Duration.seconds(99_999).toString(DurationUnit.NANOSECONDS, 15))
|
||||||
assertEquals("1.00e+14ns", Duration.seconds(100_000).toString(DurationUnit.NANOSECONDS, 9))
|
assertContains(
|
||||||
|
listOf(
|
||||||
|
"-4611686018427388000000000.000000000000ns",
|
||||||
|
"-4611686018427387904000000.000000000000ns"
|
||||||
|
),
|
||||||
|
(-Duration.milliseconds(MAX_MILLIS - 1)).toString(DurationUnit.NANOSECONDS, 15)
|
||||||
|
)
|
||||||
|
|
||||||
d = Duration.INFINITE
|
d = Duration.INFINITE
|
||||||
test(DurationUnit.DAYS, "Infinity", "Infinity")
|
test(DurationUnit.DAYS, "Infinity", "Infinity")
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
package kotlin.time
|
package kotlin.time
|
||||||
|
|
||||||
internal actual fun formatToExactDecimals(value: Double, decimals: Int): String = TODO("Wasm stdlib: Duration")
|
internal actual fun formatToExactDecimals(value: Double, decimals: Int): String = TODO("Wasm stdlib: Duration")
|
||||||
internal actual fun formatUpToDecimals(value: Double, decimals: Int): String = TODO("Wasm stdlib: Duration")
|
internal actual fun formatUpToDecimals(value: Double, decimals: Int): String = TODO("Wasm stdlib: Duration")
|
||||||
internal actual fun formatScientific(value: Double): String = TODO("Wasm stdlib: Duration")
|
|
||||||
Reference in New Issue
Block a user