Saturate overflowing values when adjusting time marks

KT-46132
This commit is contained in:
Ilya Gorbunov
2022-02-20 08:31:09 +03:00
committed by Space
parent f32e0f3cba
commit 77cf41c189
7 changed files with 194 additions and 14 deletions
@@ -54,6 +54,10 @@ public interface TimeMark {
* Returns the amount of time passed from this mark measured with the time source from which this mark was taken.
*
* Note that the value returned by this function can change on subsequent invocations.
*
* @throws IllegalArgumentException an implementation may throw if calculating the elapsed time involves
* adding a positive infinite duration to an infinitely distant past time mark or
* a negative infinite duration to an infinitely distant future time mark.
*/
public abstract fun elapsedNow(): Duration
@@ -61,6 +65,12 @@ public interface TimeMark {
* Returns a time mark on the same time source that is ahead of this time mark by the specified [duration].
*
* The returned time mark is more _late_ when the [duration] is positive, and more _early_ when the [duration] is negative.
*
* If the time mark is adjusted too far in the past or in the future, it may saturate to an infinitely distant time mark.
* In that case, [elapsedNow] will return an infinite duration elapsed from such infinitely distant mark.
*
* @throws IllegalArgumentException an implementation may throw if a positive infinite duration is added to an infinitely distant past time mark or
* a negative infinite duration is added to an infinitely distant future time mark.
*/
public open operator fun plus(duration: Duration): TimeMark = AdjustedTimeMark(this, duration)
@@ -68,6 +78,12 @@ public interface TimeMark {
* Returns a time mark on the same time source that is behind this time mark by the specified [duration].
*
* The returned time mark is more _early_ when the [duration] is positive, and more _late_ when the [duration] is negative.
*
* If the time mark is adjusted too far in the past or in the future, it may saturate to an infinitely distant time mark.
* In that case, [elapsedNow] will return an infinite duration elapsed from such infinitely distant mark.
*
* @throws IllegalArgumentException an implementation may throw if a positive infinite duration is subtracted from an infinitely distant future time mark or
* a negative infinite duration is subtracted from an infinitely distant past time mark.
*/
public open operator fun minus(duration: Duration): TimeMark = plus(-duration)
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2022 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.
*/
package kotlin.time
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.nanoseconds
// Long time reading saturation math, shared between JVM and Native
internal fun saturatingAdd(longNs: Long, duration: Duration): Long {
val durationNs = duration.inWholeNanoseconds
if ((longNs - 1) or 1 == Long.MAX_VALUE) { // MIN_VALUE or MAX_VALUE - the reading is infinite
return checkInfiniteSumDefined(longNs, duration, durationNs)
}
if ((durationNs - 1) or 1 == Long.MAX_VALUE) { // duration doesn't fit in Long nanos
return saturatingAddInHalves(longNs, duration)
}
val result = longNs + durationNs
if (((longNs xor result) and (durationNs xor result)) < 0) {
return if (longNs < 0) Long.MIN_VALUE else Long.MAX_VALUE
}
return result
}
private fun checkInfiniteSumDefined(longNs: Long, duration: Duration, durationNs: Long): Long {
if (duration.isInfinite() && (longNs xor durationNs < 0)) throw IllegalArgumentException("Summing infinities of different signs")
return longNs
}
private fun saturatingAddInHalves(longNs: Long, duration: Duration): Long {
val half = duration / 2
if ((half.inWholeNanoseconds - 1) or 1 == Long.MAX_VALUE) {
// this will definitely saturate
return (longNs + duration.toDouble(DurationUnit.NANOSECONDS)).toLong()
} else {
return saturatingAdd(saturatingAdd(longNs, half), half)
}
}
internal fun saturatingDiff(valueNs: Long, originNs: Long): Duration {
if ((originNs - 1) or 1 == Long.MAX_VALUE) { // MIN_VALUE or MAX_VALUE
return -(originNs.toDuration(DurationUnit.DAYS)) // saturate to infinity
}
val result = valueNs - originNs
if ((result xor valueNs) and (result xor originNs).inv() < 0) {
val resultMs = valueNs / NANOS_IN_MILLIS - originNs / NANOS_IN_MILLIS
val resultNs = valueNs % NANOS_IN_MILLIS - originNs % NANOS_IN_MILLIS
return resultMs.milliseconds + resultNs.nanoseconds
}
return result.nanoseconds
}