From 3c37bbaf6452058fae03225b2661170407fccd21 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 24 Dec 2020 04:07:02 +0300 Subject: [PATCH] Duration: normalize to avoid negative zeroes (KT-44168) The current comparison method of inline classes makes durations of positive and negative zeroes non-equal. To avoid that, normalize the duration value upon construction preventing negative zero being stored. --- libraries/stdlib/src/kotlin/time/Duration.kt | 15 +++++++----- libraries/stdlib/test/time/DurationTest.kt | 25 +++++++++++++++++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index a8bbdccd708..da0c52f7ea4 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -47,7 +47,7 @@ public inline class Duration internal constructor(internal val value: Double) : // arithmetic operators /** Returns the negative of this value. */ - public operator fun unaryMinus(): Duration = Duration(-value) + public operator fun unaryMinus(): Duration = Duration((-value).normalizeZero()) /** * Returns a duration whose value is the sum of this and [other] duration values. @@ -68,28 +68,28 @@ public inline class Duration internal constructor(internal val value: Double) : * * @throws IllegalArgumentException if the operation results in a `NaN` value. */ - public operator fun times(scale: Int): Duration = Duration(value * scale) + public operator fun times(scale: Int): Duration = Duration((value * scale).normalizeZero()) /** * Returns a duration whose value is this duration value multiplied by the given [scale] number. * * @throws IllegalArgumentException if the operation results in a `NaN` value. */ - public operator fun times(scale: Double): Duration = Duration(value * scale) + public operator fun times(scale: Double): Duration = Duration((value * scale).normalizeZero()) /** * Returns a duration whose value is this duration value divided by the given [scale] number. * * @throws IllegalArgumentException if the operation results in a `NaN` value. */ - public operator fun div(scale: Int): Duration = Duration(value / scale) + public operator fun div(scale: Int): Duration = Duration((value / scale).normalizeZero()) /** * Returns a duration whose value is this duration value divided by the given [scale] number. * * @throws IllegalArgumentException if the operation results in a `NaN` value. */ - public operator fun div(scale: Double): Duration = Duration(value / scale) + public operator fun div(scale: Double): Duration = Duration((value / scale).normalizeZero()) /** Returns a number that is the ratio of this and [other] duration values. */ public operator fun div(other: Duration): Double = this.value / other.value @@ -384,7 +384,7 @@ public fun Long.toDuration(unit: DurationUnit): Duration = toDouble().toDuration */ @SinceKotlin("1.3") @ExperimentalTime -public fun Double.toDuration(unit: DurationUnit): Duration = Duration(convertDurationUnit(this, unit, storageUnit)) +public fun Double.toDuration(unit: DurationUnit): Duration = Duration(convertDurationUnit(this, unit, storageUnit).normalizeZero()) // constructing from number of units // extension properties @@ -540,6 +540,9 @@ public inline operator fun Int.times(duration: Duration): Duration = duration * public inline operator fun Double.times(duration: Duration): Duration = duration * this +@kotlin.internal.InlineOnly +private inline fun Double.normalizeZero(): Double = this + 0.0 + internal expect fun formatToExactDecimals(value: Double, decimals: Int): String internal expect fun formatUpToDecimals(value: Double, decimals: Int): String internal expect fun formatScientific(value: Double): String \ No newline at end of file diff --git a/libraries/stdlib/test/time/DurationTest.kt b/libraries/stdlib/test/time/DurationTest.kt index 49a4b81f59b..de81bc88292 100644 --- a/libraries/stdlib/test/time/DurationTest.kt +++ b/libraries/stdlib/test/time/DurationTest.kt @@ -7,7 +7,6 @@ package test.time import test.numbers.assertAlmostEquals -import test.* import kotlin.native.concurrent.SharedImmutable import kotlin.test.* import kotlin.time.* @@ -218,6 +217,30 @@ class DurationTest { assertEquals(zero, zero.absoluteValue) } + @Test + fun negativeZero() { + fun equivalentToZero(value: Duration) { + assertEquals(Duration.ZERO, value) + assertEquals(Duration.ZERO, value.absoluteValue) + assertEquals(value, value.absoluteValue) + assertEquals(value, value.absoluteValue) + assertFalse(value.isNegative()) + assertFalse(value.isPositive()) + assertEquals(Duration.ZERO.toString(), value.toString()) + assertEquals(Duration.ZERO.toIsoString(), value.toIsoString()) + assertEquals(Duration.ZERO.inSeconds, value.inSeconds) + assertEquals(0, Duration.ZERO.compareTo(value)) + assertEquals(0, Duration.ZERO.inNanoseconds.compareTo(value.inNanoseconds)) + } + equivalentToZero((-0.0).seconds) + equivalentToZero((-0.0).toDuration(DurationUnit.DAYS)) + equivalentToZero(-Duration.ZERO) + equivalentToZero((-1).seconds / Double.POSITIVE_INFINITY) + equivalentToZero(0.seconds / -1) + equivalentToZero((-1).seconds * 0.0) + equivalentToZero(0.seconds * -1) + } + @Test fun addition() {