Store Duration value as a multi-range Long
Now precision loss happens at bigger durations. This changes the binary API due different underlying type of Duration.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTime::class)
|
||||
@file:Suppress("INVISIBLE_MEMBER")
|
||||
package test.time
|
||||
|
||||
import test.numbers.assertAlmostEquals
|
||||
@@ -12,26 +13,40 @@ import kotlin.test.*
|
||||
import kotlin.time.*
|
||||
import kotlin.random.*
|
||||
|
||||
@SharedImmutable
|
||||
private val expectStorageUnit = DurationUnit.NANOSECONDS
|
||||
@SharedImmutable
|
||||
private val units = DurationUnit.values()
|
||||
|
||||
class DurationTest {
|
||||
|
||||
// construction white-box testing
|
||||
@Test
|
||||
fun construction() {
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
repeat(100) {
|
||||
val value = Random.nextInt(1_000_000)
|
||||
val unit = units.random()
|
||||
val expected = convertDurationUnit(value.toDouble(), unit, expectStorageUnit)
|
||||
assertEquals(expected, value.toDuration(unit).value)
|
||||
assertEquals(expected, value.toLong().toDuration(unit).value)
|
||||
assertEquals(expected, value.toDouble().toDuration(unit).value)
|
||||
// nanosecond precision
|
||||
val testValues = listOf(0L, 1L, Long.MAX_VALUE / 2) + List(100) { Random.nextLong(0, Long.MAX_VALUE / 2) }
|
||||
for (value in testValues) {
|
||||
assertEquals(value, value.toDuration(DurationUnit.NANOSECONDS).toLongNanoseconds())
|
||||
assertEquals(-value, -value.toDuration(DurationUnit.NANOSECONDS).toLongNanoseconds())
|
||||
}
|
||||
// expressible as long nanoseconds but stored as milliseconds
|
||||
for (delta in testValues) {
|
||||
val value = 1L.shl(62) + delta
|
||||
val expected = value - (value % 1_000_000)
|
||||
assertEquals(expected, value.toDuration(DurationUnit.NANOSECONDS).toLongNanoseconds())
|
||||
assertEquals(-expected, -value.toDuration(DurationUnit.NANOSECONDS).toLongNanoseconds())
|
||||
}
|
||||
// any int value of small units can always be represented in nanoseconds
|
||||
for (unit in units.filter { it <= DurationUnit.SECONDS }) {
|
||||
val scale = convertDurationUnitOverflow(1L, unit, DurationUnit.NANOSECONDS)
|
||||
repeat(100) {
|
||||
val value = Random.nextInt()
|
||||
assertEquals(value * scale, value.toDuration(unit).toLongNanoseconds())
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(Long.MAX_VALUE / 1000, Long.MAX_VALUE.toDuration(DurationUnit.MICROSECONDS).toLongMilliseconds())
|
||||
assertEquals(Long.MAX_VALUE / 1000 * 1000, Long.MAX_VALUE.toDuration(DurationUnit.MICROSECONDS).toLong(DurationUnit.MICROSECONDS))
|
||||
|
||||
assertEquals(Duration.INFINITE, (Long.MAX_VALUE / 2).toDuration(DurationUnit.MILLISECONDS))
|
||||
assertEquals(-Duration.INFINITE, (Long.MIN_VALUE / 2).toDuration(DurationUnit.MILLISECONDS))
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { Double.NaN.toDuration(DurationUnit.SECONDS) }
|
||||
}
|
||||
@@ -64,6 +79,26 @@ class DurationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun comparison() {
|
||||
run {
|
||||
val d1 = Duration.nanoseconds(Long.MAX_VALUE / 2 + 1)
|
||||
val d2 = Duration.nanoseconds(Long.MAX_VALUE / 2)
|
||||
assertTrue(d1 >= d2, "same sign, different ranges")
|
||||
// TODO: Address this contradiction
|
||||
// assertTrue(d1.toLongNanoseconds() >= d2.toLongNanoseconds())
|
||||
|
||||
val d3 = Duration.nanoseconds(Long.MAX_VALUE / 2 - 1)
|
||||
assertTrue(d2 > d3, "same sign, same range nanos")
|
||||
|
||||
val d4 = Duration.nanoseconds(Long.MAX_VALUE)
|
||||
assertTrue(d4 > d1, "same sign, same range millis")
|
||||
|
||||
assertTrue(d2 > -d1, "different signs, different ranges")
|
||||
assertTrue(d1 > -d4, "different signs, same ranges")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun conversionFromNumber() {
|
||||
@@ -127,6 +162,15 @@ class DurationTest {
|
||||
|
||||
assertAlmostEquals(Duration.convert(value.toDouble(), unit, unit2), value.toDuration(unit).toDouble(unit2))
|
||||
}
|
||||
|
||||
for (unit in units) {
|
||||
assertEquals(Long.MAX_VALUE, Duration.INFINITE.toLong(unit))
|
||||
assertEquals(Int.MAX_VALUE, Duration.INFINITE.toInt(unit))
|
||||
assertEquals(Double.POSITIVE_INFINITY, Duration.INFINITE.toDouble(unit))
|
||||
assertEquals(Long.MIN_VALUE, (-Duration.INFINITE).toLong(unit))
|
||||
assertEquals(Int.MIN_VALUE, (-Duration.INFINITE).toInt(unit))
|
||||
assertEquals(Double.NEGATIVE_INFINITY, (-Duration.INFINITE).toDouble(unit))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -248,6 +292,14 @@ class DurationTest {
|
||||
assertEquals(Duration.days(0.5), Duration.hours(6) + Duration.minutes(360))
|
||||
assertEquals(Duration.seconds(0.5), Duration.milliseconds(200) + Duration.microseconds(300_000))
|
||||
|
||||
for (value in listOf(Duration.ZERO, Duration.nanoseconds(1), Duration.days(500 * 365))) {
|
||||
for (inf in listOf(Duration.INFINITE, -Duration.INFINITE)) {
|
||||
for (result in listOf(inf + inf, inf + value, inf + (-value), value + inf, (-value) + inf)) {
|
||||
assertEquals(inf, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { Duration.INFINITE + (-Duration.INFINITE) }
|
||||
}
|
||||
|
||||
@@ -255,6 +307,38 @@ class DurationTest {
|
||||
fun subtraction() {
|
||||
assertEquals(Duration.hours(10), Duration.days(0.5) - Duration.minutes(120))
|
||||
assertEquals(Duration.milliseconds(850), Duration.seconds(1) - Duration.milliseconds(150))
|
||||
assertEquals(Duration.milliseconds(1150), Duration.seconds(1) - Duration.milliseconds(-150))
|
||||
assertEquals(Duration.milliseconds(1), Duration.microseconds(Long.MAX_VALUE) - Duration.microseconds(Long.MAX_VALUE - 1_000))
|
||||
|
||||
run {
|
||||
val offset = 2_000_000L
|
||||
val value = Long.MAX_VALUE / 2 + offset
|
||||
val base = Duration.nanoseconds(value)
|
||||
val baseNs = base.toLongMilliseconds() * 1_000_000
|
||||
assertEquals(baseNs, base.toLongNanoseconds()) // base stored as millis
|
||||
|
||||
val smallDeltas = listOf(1L, 2L, 1000L, offset / 2 - 1) + List(10) { Random.nextLong(offset / 2) }
|
||||
for (smallDeltaNs in smallDeltas) {
|
||||
assertEquals(base, base - Duration.nanoseconds(smallDeltaNs), "delta: $smallDeltaNs")
|
||||
}
|
||||
|
||||
val deltas = listOf(offset + 1L, offset + 1500L) +
|
||||
List(10) { Random.nextLong(offset + 1500, offset + 10000) } +
|
||||
List(100) { Random.nextLong(offset + 1500, Long.MAX_VALUE / 2) }
|
||||
for (deltaNs in deltas) {
|
||||
val delta = Duration.nanoseconds(deltaNs)
|
||||
assertEquals(deltaNs, delta.toLongNanoseconds())
|
||||
assertEquals(baseNs - deltaNs, (base - delta).toLongNanoseconds(), "base: $baseNs, delta: $deltaNs")
|
||||
}
|
||||
}
|
||||
|
||||
for (value in listOf(Duration.ZERO, Duration.nanoseconds(1), Duration.days(500 * 365))) {
|
||||
for (inf in listOf(Duration.INFINITE, -Duration.INFINITE)) {
|
||||
for (result in listOf(inf - (-inf), inf - value, inf - (-value), value - (-inf), (-value) - (-inf))) {
|
||||
assertEquals(inf, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { Duration.INFINITE - Duration.INFINITE }
|
||||
}
|
||||
@@ -272,8 +356,13 @@ class DurationTest {
|
||||
assertEquals(Duration.ZERO, 0 * Duration.hours(1))
|
||||
assertEquals(Duration.ZERO, Duration.seconds(1) * 0.0)
|
||||
|
||||
assertEquals(Duration.INFINITE, Duration.INFINITE * Double.POSITIVE_INFINITY)
|
||||
assertEquals(Duration.INFINITE, Duration.INFINITE * Double.MIN_VALUE)
|
||||
assertEquals(-Duration.INFINITE, Duration.INFINITE * Double.NEGATIVE_INFINITY)
|
||||
assertEquals(-Duration.INFINITE, Duration.INFINITE * -1)
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { Duration.INFINITE * 0 }
|
||||
assertFailsWith<IllegalArgumentException> { 0 * Duration.INFINITE }
|
||||
assertFailsWith<IllegalArgumentException> { 0.0 * Duration.INFINITE }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -282,10 +371,18 @@ class DurationTest {
|
||||
assertEquals(Duration.minutes(60), Duration.days(1) / 24.0)
|
||||
assertEquals(Duration.seconds(20), Duration.minutes(2) / 6)
|
||||
|
||||
assertEquals(Duration.INFINITE, Duration.seconds(1) / 0.0)
|
||||
assertEquals(Duration.INFINITE, Duration.seconds(1) / 0)
|
||||
assertEquals(-Duration.INFINITE, -Duration.seconds(1) / 0.0)
|
||||
assertEquals(Duration.INFINITE, -Duration.seconds(1) / (-0.0))
|
||||
|
||||
assertEquals(Duration.INFINITE, Duration.INFINITE / Int.MAX_VALUE)
|
||||
assertEquals(Duration.INFINITE, -Duration.INFINITE / Int.MIN_VALUE)
|
||||
assertEquals(-Duration.INFINITE, Duration.INFINITE / -1)
|
||||
assertEquals(Duration.INFINITE, Duration.INFINITE / Double.MAX_VALUE)
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { Duration.INFINITE / Double.POSITIVE_INFINITY }
|
||||
assertFailsWith<IllegalArgumentException> { Duration.ZERO / 0 }
|
||||
assertFailsWith<IllegalArgumentException> { Duration.ZERO / 0.0 }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -293,6 +390,7 @@ class DurationTest {
|
||||
assertEquals(24.0, Duration.days(1) / Duration.hours(1))
|
||||
assertEquals(0.1, Duration.minutes(9) / Duration.hours(1.5))
|
||||
assertEquals(50.0, Duration.microseconds(1) / Duration.nanoseconds(20))
|
||||
assertEquals(299.0, Duration.days(365 * 299) / Duration.days(365))
|
||||
|
||||
assertTrue((Duration.INFINITE / Duration.INFINITE).isNaN())
|
||||
}
|
||||
@@ -354,14 +452,15 @@ class DurationTest {
|
||||
d -= Duration.milliseconds(1678)
|
||||
test(DurationUnit.MICROSECONDS, "921us", "920.5us", "920.52us", "920.516us")
|
||||
d -= Duration.microseconds(920)
|
||||
// sub-nanosecond precision errors
|
||||
test(DurationUnit.NANOSECONDS, "516ns", "516.3ns", "516.34ns", "516.344ns", "516.3438ns")
|
||||
// no sub-nanosecond precision
|
||||
test(DurationUnit.NANOSECONDS, "516ns", "516.0ns", "516.00ns", "516.000ns", "516.0000ns")
|
||||
d = (d - Duration.nanoseconds(516)) / 17
|
||||
test(DurationUnit.NANOSECONDS, "0ns", "0.0ns", "0.02ns", "0.020ns", "0.0202ns")
|
||||
test(DurationUnit.NANOSECONDS, "0ns", "0.0ns", "0.00ns", "0.000ns", "0.0000ns")
|
||||
|
||||
d = Duration.nanoseconds(Double.MAX_VALUE)
|
||||
test(DurationUnit.DAYS, "2.08e+294d")
|
||||
test(DurationUnit.NANOSECONDS, "1.80e+308ns")
|
||||
// infinite
|
||||
// d = Duration.nanoseconds(Double.MAX_VALUE)
|
||||
// test(DurationUnit.DAYS, "2.08e+294d")
|
||||
// test(DurationUnit.NANOSECONDS, "1.80e+308ns")
|
||||
|
||||
assertEquals("0.500000000000s", Duration.seconds(0.5).toString(DurationUnit.SECONDS, 100))
|
||||
assertEquals("99999000000000.000000000000ns", Duration.seconds(99_999).toString(DurationUnit.NANOSECONDS, 15))
|
||||
@@ -417,27 +516,29 @@ class DurationTest {
|
||||
test(Duration.microseconds(1.005), "1.01us", "1.00us")
|
||||
|
||||
test(Duration.nanoseconds(950.5), "951ns", "950ns")
|
||||
test(Duration.nanoseconds(85.23), "85.2ns")
|
||||
test(Duration.nanoseconds(8.235), "8.24ns", "8.23ns")
|
||||
test(Duration.nanoseconds(1.3), "1.30ns")
|
||||
test(Duration.nanoseconds(85.23), "85.0ns")
|
||||
test(Duration.nanoseconds(8.235), "8.00ns")
|
||||
test(Duration.nanoseconds(1.3), "1.00ns")
|
||||
|
||||
test(Duration.nanoseconds(0.75), "0.75ns")
|
||||
test(Duration.nanoseconds(0.7512), "0.7512ns")
|
||||
test(Duration.nanoseconds(0.023), "0.023ns")
|
||||
test(Duration.nanoseconds(0.0034), "0.0034ns")
|
||||
test(Duration.nanoseconds(0.0000035), "0.0000035ns")
|
||||
// equal to zero
|
||||
// test(Duration.nanoseconds(0.75), "0.75ns")
|
||||
// test(Duration.nanoseconds(0.7512), "0.7512ns")
|
||||
// test(Duration.nanoseconds(0.023), "0.023ns")
|
||||
// test(Duration.nanoseconds(0.0034), "0.0034ns")
|
||||
// test(Duration.nanoseconds(0.0000035), "0.0000035ns")
|
||||
|
||||
test(Duration.ZERO, "0s")
|
||||
test(Duration.days(365) * 10000, "3650000d")
|
||||
test(Duration.days(300) * 100000, "3.00e+7d")
|
||||
test(Duration.days(365) * 100000, "3.65e+7d")
|
||||
|
||||
val universeAge = Duration.days(365.25) * 13.799e9
|
||||
val planckTime = Duration.seconds(5.4e-44)
|
||||
// all infinite
|
||||
// val universeAge = Duration.days(365.25) * 13.799e9
|
||||
// val planckTime = Duration.seconds(5.4e-44)
|
||||
|
||||
test(universeAge, "5.04e+12d")
|
||||
test(planckTime, "5.40e-44s")
|
||||
test(Duration.nanoseconds(Double.MAX_VALUE), "2.08e+294d")
|
||||
// test(universeAge, "5.04e+12d")
|
||||
// test(planckTime, "5.40e-44s")
|
||||
// test(Duration.nanoseconds(Double.MAX_VALUE), "2.08e+294d")
|
||||
test(Duration.INFINITE, "Infinity")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user