KT-58046 Generalize saturating duration math to arbitrary units

This commit is contained in:
Ilya Gorbunov
2023-04-09 16:42:46 +02:00
committed by Space Team
parent 17d977977c
commit 7a6947ad35
3 changed files with 48 additions and 42 deletions
@@ -19,13 +19,13 @@ internal actual object MonotonicTimeSource : TimeSource.WithComparableMarks {
actual override fun markNow(): ValueTimeMark = ValueTimeMark(read()) actual override fun markNow(): ValueTimeMark = ValueTimeMark(read())
actual fun elapsedFrom(timeMark: ValueTimeMark): Duration = actual fun elapsedFrom(timeMark: ValueTimeMark): Duration =
saturatingDiff(read(), timeMark.reading) saturatingDiff(read(), timeMark.reading, DurationUnit.NANOSECONDS)
actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration = actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration =
saturatingOriginsDiff(one.reading, another.reading) saturatingOriginsDiff(one.reading, another.reading, DurationUnit.NANOSECONDS)
actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark =
ValueTimeMark(saturatingAdd(timeMark.reading, duration)) ValueTimeMark(saturatingAdd(timeMark.reading, DurationUnit.NANOSECONDS, duration))
} }
@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility @Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
@@ -16,13 +16,13 @@ internal actual object MonotonicTimeSource : TimeSource.WithComparableMarks {
actual override fun markNow(): ValueTimeMark = ValueTimeMark(read()) actual override fun markNow(): ValueTimeMark = ValueTimeMark(read())
actual fun elapsedFrom(timeMark: ValueTimeMark): Duration = actual fun elapsedFrom(timeMark: ValueTimeMark): Duration =
saturatingDiff(read(), timeMark.reading) saturatingDiff(read(), timeMark.reading, DurationUnit.NANOSECONDS)
actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration = actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration =
saturatingOriginsDiff(one.reading, another.reading) saturatingOriginsDiff(one.reading, another.reading, DurationUnit.NANOSECONDS)
actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark = actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark =
ValueTimeMark(saturatingAdd(timeMark.reading, duration)) ValueTimeMark(saturatingAdd(timeMark.reading, DurationUnit.NANOSECONDS, duration))
} }
@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility @Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
@@ -6,67 +6,73 @@
package kotlin.time package kotlin.time
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.nanoseconds
// Long time reading saturation math, shared between JVM and Native // Long time reading saturation math, shared between JVM and Native
internal fun saturatingAdd(longNs: Long, duration: Duration): Long { internal fun saturatingAdd(value: Long, unit: DurationUnit, duration: Duration): Long {
val durationNs = duration.inWholeNanoseconds val durationInUnit = duration.toLong(unit)
if (longNs.isSaturated()) { // MIN_VALUE or MAX_VALUE - the reading is infinite if (value.isSaturated()) { // the reading is infinitely saturated
return checkInfiniteSumDefined(longNs, duration, durationNs) return checkInfiniteSumDefined(value, duration, durationInUnit)
} }
if (durationNs.isSaturated()) { // duration doesn't fit in Long nanos if (durationInUnit.isSaturated()) { // duration doesn't fit in Long units
return saturatingAddInHalves(longNs, duration) return saturatingAddInHalves(value, unit, duration)
} }
val result = longNs + durationNs val result = value + durationInUnit
if (((longNs xor result) and (durationNs xor result)) < 0) { if (((value xor result) and (durationInUnit xor result)) < 0) {
return if (longNs < 0) Long.MIN_VALUE else Long.MAX_VALUE return if (value < 0) Long.MIN_VALUE else Long.MAX_VALUE
} }
return result return result
} }
private fun checkInfiniteSumDefined(longNs: Long, duration: Duration, durationNs: Long): Long { private fun checkInfiniteSumDefined(value: Long, duration: Duration, durationInUnit: Long): Long {
if (duration.isInfinite() && (longNs xor durationNs < 0)) throw IllegalArgumentException("Summing infinities of different signs") if (duration.isInfinite() && (value xor durationInUnit < 0)) throw IllegalArgumentException("Summing infinities of different signs")
return longNs return value
} }
private fun saturatingAddInHalves(longNs: Long, duration: Duration): Long { private fun saturatingAddInHalves(value: Long, unit: DurationUnit, duration: Duration): Long {
val half = duration / 2 val half = duration / 2
if (half.inWholeNanoseconds.isSaturated()) { val halfInUnit = half.toLong(unit)
// this will definitely saturate if (halfInUnit.isSaturated()) {
return (longNs + duration.toDouble(DurationUnit.NANOSECONDS)).toLong() return halfInUnit // value + inf == inf, return saturated value
} else { } else {
return saturatingAdd(saturatingAdd(longNs, half), duration - half) return saturatingAdd(saturatingAdd(value, unit, half), unit, duration - half)
} }
} }
internal fun saturatingDiff(valueNs: Long, originNs: Long): Duration { private fun infinityOfSign(value: Long): Duration = if (value < 0) Duration.NEG_INFINITE else Duration.INFINITE
if (originNs.isSaturated()) { // MIN_VALUE or MAX_VALUE
return -(originNs.toDuration(DurationUnit.DAYS)) // saturate to infinity internal fun saturatingDiff(valueNs: Long, origin: Long, unit: DurationUnit): Duration {
if (origin.isSaturated()) { // MIN_VALUE or MAX_VALUE
return -infinityOfSign(origin)
} }
return saturatingFiniteDiff(valueNs, originNs) return saturatingFiniteDiff(valueNs, origin, unit)
} }
internal fun saturatingOriginsDiff(origin1Ns: Long, origin2Ns: Long): Duration { internal fun saturatingOriginsDiff(origin1: Long, origin2: Long, unit: DurationUnit): Duration {
if (origin2Ns.isSaturated()) { // MIN_VALUE or MAX_VALUE if (origin2.isSaturated()) {
if (origin1Ns == origin2Ns) return Duration.ZERO // saturated values of the same sign are considered equal if (origin1 == origin2) return Duration.ZERO // saturated values of the same sign are considered equal
return -(origin2Ns.toDuration(DurationUnit.DAYS)) // saturate to infinity return -infinityOfSign(origin2)
} }
if (origin1Ns.isSaturated()) { if (origin1.isSaturated()) {
return origin1Ns.toDuration(DurationUnit.DAYS) return infinityOfSign(origin1)
} }
return saturatingFiniteDiff(origin1Ns, origin2Ns) return saturatingFiniteDiff(origin1, origin2, unit)
} }
private fun saturatingFiniteDiff(value1Ns: Long, value2Ns: Long): Duration { private fun saturatingFiniteDiff(value1: Long, value2: Long, unit: DurationUnit): Duration {
val result = value1Ns - value2Ns val result = value1 - value2
if ((result xor value1Ns) and (result xor value2Ns).inv() < 0) { if ((result xor value1) and (result xor value2).inv() < 0) { // Long overflow
val resultMs = value1Ns / NANOS_IN_MILLIS - value2Ns / NANOS_IN_MILLIS if (unit < DurationUnit.MILLISECONDS) {
val resultNs = value1Ns % NANOS_IN_MILLIS - value2Ns % NANOS_IN_MILLIS val unitsInMilli = convertDurationUnit(1, DurationUnit.MILLISECONDS, unit)
return resultMs.milliseconds + resultNs.nanoseconds val resultMs = value1 / unitsInMilli - value2 / unitsInMilli
val resultUnit = value1 % unitsInMilli - value2 % unitsInMilli
return resultMs.milliseconds + resultUnit.toDuration(unit)
} else {
return -infinityOfSign(result)
}
} }
return result.nanoseconds return result.toDuration(unit)
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")