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.
This commit is contained in:
Ilya Gorbunov
2020-12-24 04:07:02 +03:00
parent ae6f10df3b
commit 3c37bbaf64
2 changed files with 33 additions and 7 deletions
+9 -6
View File
@@ -47,7 +47,7 @@ public inline class Duration internal constructor(internal val value: Double) :
// arithmetic operators // arithmetic operators
/** Returns the negative of this value. */ /** 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. * 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. * @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. * 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. * @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. * 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. * @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. * 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. * @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. */ /** Returns a number that is the ratio of this and [other] duration values. */
public operator fun div(other: Duration): Double = this.value / other.value 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") @SinceKotlin("1.3")
@ExperimentalTime @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 // constructing from number of units
// extension properties // 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 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 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 internal expect fun formatScientific(value: Double): String
+24 -1
View File
@@ -7,7 +7,6 @@
package test.time package test.time
import test.numbers.assertAlmostEquals import test.numbers.assertAlmostEquals
import test.*
import kotlin.native.concurrent.SharedImmutable import kotlin.native.concurrent.SharedImmutable
import kotlin.test.* import kotlin.test.*
import kotlin.time.* import kotlin.time.*
@@ -218,6 +217,30 @@ class DurationTest {
assertEquals(zero, zero.absoluteValue) 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 @Test
fun addition() { fun addition() {