Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.whathecode.kotlinx.interval.TypeOperations
import kotlinx.datetime.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit


internal object InstantOperations : TypeOperations<Instant>
Expand All @@ -22,6 +23,23 @@ internal object InstantOperations : TypeOperations<Instant>
a.epochSeconds - b.epochSeconds,
a.nanosecondsOfSecond - b.nanosecondsOfSecond
)

private const val NANOS_IN_SECOND = 1_000_000_000

override fun fromDouble( double: Double ): Instant
{
val seconds = double.toLong()
val fraction = double - seconds
val nanoseconds = (NANOS_IN_SECOND * fraction).toLong()
return Instant.fromEpochSeconds( seconds, nanoseconds )
}

override fun toDouble( value: Instant ): Double
{
val seconds = value.epochSeconds
val fraction = value.nanosecondsOfSecond.toDouble() / NANOS_IN_SECOND
return seconds + fraction
}
}


Expand All @@ -36,4 +54,7 @@ internal object DurationOperations : TypeOperations<Duration>

override fun unsafeAdd( a: Duration, b: Duration ): Duration = a + b
override fun unsafeSubtract( a: Duration, b: Duration ): Duration = a - b

override fun fromDouble( double: Double ): Duration = double.milliseconds
override fun toDouble( value: Duration ): Double = value.toDouble( DurationUnit.MILLISECONDS )
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,52 @@ abstract class TypeOperationsTest<T : Comparable<T>>(
val result = typeOperations.unsafeSubtract( a, a )
assertEquals( typeOperations.additiveIdentity, result )
}

@Test
fun toDouble_fromDouble_roundtrip_returns_same_value()
{
val original = a

val toDouble = typeOperations.toDouble( original )
val fromDouble = typeOperations.fromDouble( toDouble )

assertEquals( original, fromDouble )
}

@Test
fun toDouble_fromDouble_roundtrip_for_maxima()
{
val maxima = listOf( typeOperations.minValue, typeOperations.maxValue )
maxima.forEach {
val toDouble = typeOperations.toDouble( it )
val fromDouble = typeOperations.fromDouble( toDouble )
assertEquals( it, fromDouble )
}
}

@Test
fun fromDouble_overflows_past_max()
{
val max = typeOperations.maxValue
val maxDouble = typeOperations.toDouble( max )
val pastMaxDouble = maxDouble + 1.0

val overflowMax = typeOperations.fromDouble( pastMaxDouble )

// Types either overflow, or are coerced to max value (e.g. floating points).
if ( overflowMax != max ) assertEquals( typeOperations.minValue, overflowMax )
}

@Test
fun fromDouble_overflows_past_min()
{
val min = typeOperations.minValue
val minDouble = typeOperations.toDouble( min )
val pastMinDouble = minDouble - 1.0

val overflowMin = typeOperations.fromDouble( pastMinDouble )

// Types either overflow, or are coerced to min value (e.g. floating points).
if ( overflowMin != min ) assertEquals( typeOperations.maxValue, overflowMin )
}
}
35 changes: 35 additions & 0 deletions kotlinx.interval/src/commonMain/kotlin/BasicTypeOperations.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.whathecode.kotlinx.interval

import kotlin.math.roundToInt
import kotlin.math.roundToLong
import kotlin.reflect.KClass


Expand Down Expand Up @@ -45,6 +47,9 @@ internal object ByteOperations : TypeOperations<Byte>

override fun unsafeAdd( a: Byte, b: Byte ): Byte = (a + b).toByte()
override fun unsafeSubtract( a: Byte, b: Byte ): Byte = (a - b).toByte()

override fun fromDouble( double: Double ) = double.roundToInt().toByte()
override fun toDouble( value: Byte ) = value.toDouble()
}

internal object ShortOperations : TypeOperations<Short>
Expand All @@ -56,6 +61,9 @@ internal object ShortOperations : TypeOperations<Short>

override fun unsafeAdd( a: Short, b: Short ): Short = (a + b).toShort()
override fun unsafeSubtract( a: Short, b: Short ): Short = (a - b).toShort()

override fun fromDouble( double: Double ) = double.roundToInt().toShort()
override fun toDouble( value: Short ) = value.toDouble()
}

internal object IntOperations : TypeOperations<Int>
Expand All @@ -67,6 +75,9 @@ internal object IntOperations : TypeOperations<Int>

override fun unsafeAdd( a: Int, b: Int ): Int = a + b
override fun unsafeSubtract( a: Int, b: Int ): Int = a - b

override fun fromDouble( double: Double ): Int = double.roundToInt()
override fun toDouble( value: Int ): Double = value.toDouble()
}

internal object LongOperations : TypeOperations<Long>
Expand All @@ -78,6 +89,9 @@ internal object LongOperations : TypeOperations<Long>

override fun unsafeAdd( a: Long, b: Long ): Long = a + b
override fun unsafeSubtract( a: Long, b: Long ): Long = a - b

override fun fromDouble( double: Double ): Long = double.roundToLong()
override fun toDouble( value: Long ): Double = value.toDouble()
}

internal object FloatOperations : TypeOperations<Float>
Expand All @@ -89,6 +103,9 @@ internal object FloatOperations : TypeOperations<Float>

override fun unsafeAdd( a: Float, b: Float ): Float = a + b
override fun unsafeSubtract( a: Float, b: Float ): Float = a - b

override fun fromDouble( double: Double ): Float = double.toFloat()
override fun toDouble( value: Float ): Double = value.toDouble()
}

internal object DoubleOperations : TypeOperations<Double>
Expand All @@ -100,6 +117,9 @@ internal object DoubleOperations : TypeOperations<Double>

override fun unsafeAdd( a: Double, b: Double ): Double = a + b
override fun unsafeSubtract( a: Double, b: Double ): Double = a - b

override fun fromDouble( double: Double ): Double = double
override fun toDouble( value: Double ): Double = value
}

internal object UByteOperations : TypeOperations<UByte>
Expand All @@ -111,6 +131,9 @@ internal object UByteOperations : TypeOperations<UByte>

override fun unsafeAdd( a: UByte, b: UByte ): UByte = (a + b).toUByte()
override fun unsafeSubtract( a: UByte, b: UByte ): UByte = (a - b).toUByte()

override fun fromDouble( double: Double ): UByte = double.roundToInt().toUByte()
override fun toDouble( value: UByte ): Double = value.toDouble()
}

internal object UShortOperations : TypeOperations<UShort>
Expand All @@ -122,6 +145,9 @@ internal object UShortOperations : TypeOperations<UShort>

override fun unsafeAdd( a: UShort, b: UShort ): UShort = (a + b).toUShort()
override fun unsafeSubtract( a: UShort, b: UShort ): UShort = (a - b).toUShort()

override fun fromDouble( double: Double ): UShort = double.roundToInt().toUShort()
override fun toDouble( value: UShort ): Double = value.toDouble()
}

internal object UIntOperations : TypeOperations<UInt>
Expand All @@ -133,6 +159,9 @@ internal object UIntOperations : TypeOperations<UInt>

override fun unsafeAdd( a: UInt, b: UInt ): UInt = a + b
override fun unsafeSubtract( a: UInt, b: UInt ): UInt = a - b

override fun fromDouble( double: Double ): UInt = double.toUInt()
override fun toDouble( value: UInt ): Double = value.toDouble()
}

internal object ULongOperations : TypeOperations<ULong>
Expand All @@ -144,6 +173,9 @@ internal object ULongOperations : TypeOperations<ULong>

override fun unsafeAdd( a: ULong, b: ULong ): ULong = a + b
override fun unsafeSubtract( a: ULong, b: ULong ): ULong = a - b

override fun fromDouble( double: Double ): ULong = double.toULong()
override fun toDouble( value: ULong ): Double = value.toDouble()
}

internal object CharOperations : TypeOperations<Char>
Expand All @@ -155,4 +187,7 @@ internal object CharOperations : TypeOperations<Char>

override fun unsafeAdd( a: Char, b: Char ): Char = a + b.code
override fun unsafeSubtract( a: Char, b: Char ): Char = (a - b).toChar()

override fun fromDouble( double: Double ): Char = double.roundToInt().toChar()
override fun toDouble( value: Char ): Double = value.code.toDouble()
}
12 changes: 12 additions & 0 deletions kotlinx.interval/src/commonMain/kotlin/TypeOperations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ interface TypeOperations<T : Comparable<T>>
*/
fun unsafeSubtract( a: T, b: T ): T

/**
* Convert a [double] to the closest corresponding value of [T].
* If the [double] value is larger than what can be represented by [T] ([minValue]..[maxValue]), the returned value
* will overflow.
*/
fun fromDouble( double: Double ): T

/**
* Convert [value] to a [Double], which may be lossy.
*/
fun toDouble( value: T ): Double


/**
* Determines whether this type can represent negative values.
Expand Down
Loading