Implement conversion between floating point numbers and unsigned integers
KT-27108
This commit is contained in:
committed by
Ilya Gorbunov
parent
a970de51ab
commit
1b6b44c805
@@ -5,6 +5,9 @@
|
||||
|
||||
package test.unsigned
|
||||
|
||||
import kotlin.math.nextDown
|
||||
import kotlin.math.nextUp
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sign
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.*
|
||||
@@ -99,6 +102,91 @@ class UIntTest {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun convertToFloat() {
|
||||
fun testEquals(v1: Float, v2: UInt) = assertEquals(v1, v2.toFloat())
|
||||
|
||||
testEquals(0.0f, zero)
|
||||
testEquals(1.0f, one)
|
||||
testEquals(0xFFFF_FFFF.toFloat(), max)
|
||||
|
||||
repeat(100) {
|
||||
val long = Random.nextLong(0, 0xFFFF_FFFF)
|
||||
testEquals(long.toFloat(), long.toUInt())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun convertToDouble() {
|
||||
fun testEquals(v1: Double, v2: UInt) = assertEquals(v1, v2.toDouble())
|
||||
|
||||
testEquals(0.0, zero)
|
||||
testEquals(1.0, one)
|
||||
testEquals(max.toLong().toDouble(), max)
|
||||
|
||||
repeat(100) {
|
||||
val long = Random.nextLong(0, max.toLong())
|
||||
testEquals(long.toDouble(), long.toUInt())
|
||||
}
|
||||
|
||||
fun testRounding(from: UInt, count: UInt) {
|
||||
for (x in from..(from + count)) {
|
||||
val double = x.toDouble()
|
||||
val v = double.toUInt()
|
||||
val down = double.nextDown().toUInt()
|
||||
val up = double.nextUp().toUInt()
|
||||
|
||||
assertTrue(down <= x && down <= v)
|
||||
assertTrue(up >= x && up >= v)
|
||||
|
||||
if (v > x) {
|
||||
assertTrue(v - x <= x - down, "Expected $x being closer to $v than to $down")
|
||||
} else {
|
||||
assertTrue(x - v <= up - x, "Expected $x being closer to $v than to $up")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testRounding(0u, 100u)
|
||||
testRounding(Int.MAX_VALUE.toUInt() - 10u, 100u)
|
||||
testRounding(UInt.MAX_VALUE - 100u, 100u)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun convertDoubleToUInt() {
|
||||
fun testEquals(v1: Double, v2: UInt) = assertEquals(v1.toUInt(), v2)
|
||||
|
||||
testEquals(0.0, zero)
|
||||
testEquals(-1.0, zero)
|
||||
|
||||
testEquals(-2_000_000_000_000.0, zero)
|
||||
testEquals(-(2.0.pow(UInt.SIZE_BITS + 12)), zero)
|
||||
testEquals(Double.MIN_VALUE, zero)
|
||||
testEquals(Double.NEGATIVE_INFINITY, zero)
|
||||
testEquals(Double.NaN, zero)
|
||||
|
||||
testEquals(1.0, one)
|
||||
|
||||
testEquals(2_000_000_000_000.0, max)
|
||||
testEquals(max.toDouble(), max)
|
||||
testEquals(2.0.pow(UInt.SIZE_BITS), max)
|
||||
testEquals(2.0.pow(UInt.SIZE_BITS + 12), max)
|
||||
testEquals(Double.MAX_VALUE, max)
|
||||
testEquals(Double.POSITIVE_INFINITY, max)
|
||||
|
||||
repeat(100) {
|
||||
val v = -Random.nextDouble(until = 2.0.pow(UInt.SIZE_BITS + 8))
|
||||
testEquals(v, zero)
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val v = Random.nextDouble(from = max.toDouble(), until = 2.0.pow(UInt.SIZE_BITS + 8))
|
||||
testEquals(v, max)
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val v = Random.nextDouble(until = max.toDouble())
|
||||
testEquals(v, v.toLong().toUInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package test.unsigned
|
||||
|
||||
import kotlin.math.sign
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.*
|
||||
|
||||
@@ -99,6 +99,146 @@ class ULongTest {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun convertToFloat() {
|
||||
fun testEquals(v1: Float, v2: ULong) = assertEquals(v1, v2.toFloat())
|
||||
|
||||
testEquals(0.0f, zero)
|
||||
testEquals(1.0f, one)
|
||||
|
||||
testEquals(2.0f.pow(ULong.SIZE_BITS) - 1, max)
|
||||
testEquals(2.0f * Long.MAX_VALUE + 1, max)
|
||||
|
||||
repeat(100) {
|
||||
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
||||
testEquals(long.toFloat(), long.toULong())
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
||||
val float = Long.MAX_VALUE.toFloat() + long.toFloat() // We lose accuracy here, hence `eps` is used.
|
||||
val ulong = Long.MAX_VALUE.toULong() + long.toULong()
|
||||
|
||||
val eps = 1e+13
|
||||
assertTrue(abs(float - ulong.toFloat()) < eps)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun convertToDouble() {
|
||||
fun testEquals(v1: Double, v2: ULong) = assertEquals(v1, v2.toDouble())
|
||||
|
||||
testEquals(0.0, zero)
|
||||
testEquals(1.0, one)
|
||||
|
||||
testEquals(2.0.pow(ULong.SIZE_BITS) - 1, max)
|
||||
testEquals(2.0 * Long.MAX_VALUE + 1, max)
|
||||
|
||||
repeat(100) {
|
||||
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
||||
testEquals(long.toDouble(), long.toULong())
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
||||
val double = Long.MAX_VALUE.toDouble() + long.toDouble() // We lose accuracy here, hence `eps` is used.
|
||||
val ulong = Long.MAX_VALUE.toULong() + long.toULong()
|
||||
|
||||
val eps = 1e+4
|
||||
assertTrue(abs(double - ulong.toDouble()) < eps)
|
||||
}
|
||||
|
||||
fun testRounding(from: ULong, count: UInt) {
|
||||
for (x in from..(from + count)) {
|
||||
val double = x.toDouble()
|
||||
val v = double.toULong()
|
||||
val down = double.nextDown().toULong()
|
||||
val up = double.nextUp().toULong()
|
||||
|
||||
assertTrue(down <= x && down <= v)
|
||||
assertTrue(up >= x && up >= v)
|
||||
|
||||
if (v > x) {
|
||||
assertTrue(v - x <= x - down, "Expected $x being closer to $v than to $down")
|
||||
} else {
|
||||
assertTrue(x - v <= up - x, "Expected $x being closer to $v than to $up")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testRounding(0u, 100u)
|
||||
testRounding(Long.MAX_VALUE.toULong() - 520u, 100u)
|
||||
testRounding(ULong.MAX_VALUE - 10000u, 10000u)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun convertDoubleToULong() {
|
||||
fun testEquals(v1: Double, v2: ULong) = assertEquals(v1.toULong(), v2)
|
||||
|
||||
testEquals(0.0, zero)
|
||||
testEquals(-1.0, zero)
|
||||
|
||||
testEquals(-2_000_000_000_000.0, zero)
|
||||
testEquals(-(2.0.pow(ULong.SIZE_BITS + 5)), zero)
|
||||
testEquals(Double.MIN_VALUE, zero)
|
||||
testEquals(Double.NEGATIVE_INFINITY, zero)
|
||||
testEquals(Double.NaN, zero)
|
||||
|
||||
testEquals(1.0, one)
|
||||
|
||||
testEquals(2_000_000_000_000_000_000_000.0, max)
|
||||
testEquals(2.0.pow(ULong.SIZE_BITS), max)
|
||||
testEquals(2.0.pow(ULong.SIZE_BITS + 5), max)
|
||||
testEquals(Double.MAX_VALUE, max)
|
||||
testEquals(Double.POSITIVE_INFINITY, max)
|
||||
|
||||
repeat(100) {
|
||||
val v = -Random.nextDouble(until = 2.0.pow(ULong.SIZE_BITS + 8))
|
||||
testEquals(v, zero)
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val v = Random.nextDouble(from = max.toDouble(), until = 2.0.pow(ULong.SIZE_BITS + 8))
|
||||
testEquals(v, max)
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val v = Random.nextDouble() * Long.MAX_VALUE
|
||||
testEquals(v, v.toLong().toULong())
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val diff = Random.nextDouble() * Long.MAX_VALUE
|
||||
val d = Long.MAX_VALUE.toDouble() + diff
|
||||
val ul = Long.MAX_VALUE.toULong() + diff.toLong().toULong()
|
||||
val eps = 1e-6
|
||||
|
||||
assertTrue(d.toULong() / ul <= 1u)
|
||||
assertTrue(d / ul.toDouble() < 1 + eps)
|
||||
assertTrue(d / ul.toDouble() > 1 - eps)
|
||||
}
|
||||
|
||||
fun testTrailingBits(v: Double, count: Int) {
|
||||
val mask = (1uL shl count) - 1uL
|
||||
assertEquals(0uL, v.toULong() and mask)
|
||||
}
|
||||
|
||||
var withTrailingZeros = 2.0.pow(64)
|
||||
repeat(10) {
|
||||
withTrailingZeros = withTrailingZeros.nextDown()
|
||||
testTrailingBits(withTrailingZeros, 11)
|
||||
}
|
||||
|
||||
withTrailingZeros = 2.0.pow(63)
|
||||
repeat(10) {
|
||||
testTrailingBits(withTrailingZeros, 11)
|
||||
withTrailingZeros = withTrailingZeros.nextUp()
|
||||
}
|
||||
|
||||
repeat(100) {
|
||||
val msb = Random.nextInt(53, 64)
|
||||
val v = 2.0.pow(msb) * (1.0 + Random.nextDouble())
|
||||
testTrailingBits(v, msb - 52)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,11 @@ public inline class UByte @PublishedApi internal constructor(@PublishedApi inter
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toULong(): ULong = ULong(data.toLong() and 0xFF)
|
||||
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toFloat(): Float = this.toInt().toFloat()
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toDouble(): Double = this.toInt().toDouble()
|
||||
|
||||
public override fun toString(): String = toInt().toString()
|
||||
|
||||
}
|
||||
@@ -196,3 +201,12 @@ public inline fun Int.toUByte(): UByte = UByte(this.toByte())
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Long.toUByte(): UByte = UByte(this.toByte())
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Float.toUByte(): UByte = doubleToUByte(this.toDouble())
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Double.toUByte(): UByte = doubleToUByte(this)
|
||||
|
||||
@@ -182,6 +182,11 @@ public inline class UInt @PublishedApi internal constructor(@PublishedApi intern
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toULong(): ULong = ULong(data.toLong() and 0xFFFF_FFFF)
|
||||
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toFloat(): Float = this.toDouble().toFloat()
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toDouble(): Double = uintToDouble(data)
|
||||
|
||||
public override fun toString(): String = toLong().toString()
|
||||
|
||||
}
|
||||
@@ -202,3 +207,12 @@ public inline fun Int.toUInt(): UInt = UInt(this)
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Long.toUInt(): UInt = UInt(this.toInt())
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Float.toUInt(): UInt = doubleToUInt(this.toDouble())
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Double.toUInt(): UInt = doubleToUInt(this)
|
||||
|
||||
@@ -182,6 +182,11 @@ public inline class ULong @PublishedApi internal constructor(@PublishedApi inter
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toULong(): ULong = this
|
||||
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toFloat(): Float = this.toDouble().toFloat()
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toDouble(): Double = ulongToDouble(data)
|
||||
|
||||
public override fun toString(): String = ulongToString(data)
|
||||
|
||||
}
|
||||
@@ -202,3 +207,12 @@ public inline fun Int.toULong(): ULong = ULong(this.toLong())
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Long.toULong(): ULong = ULong(this)
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Float.toULong(): ULong = doubleToULong(this.toDouble())
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Double.toULong(): ULong = doubleToULong(this)
|
||||
|
||||
@@ -176,6 +176,11 @@ public inline class UShort @PublishedApi internal constructor(@PublishedApi inte
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toULong(): ULong = ULong(data.toLong() and 0xFFFF)
|
||||
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toFloat(): Float = this.toInt().toFloat()
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun toDouble(): Double = this.toInt().toDouble()
|
||||
|
||||
public override fun toString(): String = toInt().toString()
|
||||
|
||||
}
|
||||
@@ -196,3 +201,12 @@ public inline fun Int.toUShort(): UShort = UShort(this.toShort())
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Long.toUShort(): UShort = UShort(this.toShort())
|
||||
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Float.toUShort(): UShort = doubleToUShort(this.toDouble())
|
||||
@SinceKotlin("1.3")
|
||||
@ExperimentalUnsignedTypes
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Double.toUShort(): UShort = doubleToUShort(this)
|
||||
|
||||
@@ -63,6 +63,50 @@ internal fun ulongRemainder(v1: ULong, v2: ULong): ULong {
|
||||
}
|
||||
|
||||
|
||||
@PublishedApi
|
||||
internal fun doubleToUByte(v: Double): UByte = when {
|
||||
v.isNaN() -> 0u
|
||||
v <= UByte.MIN_VALUE.toDouble() -> UByte.MIN_VALUE
|
||||
v >= UByte.MAX_VALUE.toDouble() -> UByte.MAX_VALUE
|
||||
else -> v.toInt().toUByte()
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun doubleToUShort(v: Double): UShort = when {
|
||||
v.isNaN() -> 0u
|
||||
v <= UShort.MIN_VALUE.toDouble() -> UShort.MIN_VALUE
|
||||
v >= UShort.MAX_VALUE.toDouble() -> UShort.MAX_VALUE
|
||||
else -> v.toInt().toUShort()
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun doubleToUInt(v: Double): UInt = when {
|
||||
v.isNaN() -> 0u
|
||||
v <= UInt.MIN_VALUE.toDouble() -> UInt.MIN_VALUE
|
||||
v >= UInt.MAX_VALUE.toDouble() -> UInt.MAX_VALUE
|
||||
v <= Int.MAX_VALUE -> v.toInt().toUInt()
|
||||
else -> (v - Int.MAX_VALUE).toInt().toUInt() + Int.MAX_VALUE.toUInt() // Int.MAX_VALUE < v < UInt.MAX_VALUE
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun doubleToULong(v: Double): ULong = when {
|
||||
v.isNaN() -> 0u
|
||||
v <= ULong.MIN_VALUE.toDouble() -> ULong.MIN_VALUE
|
||||
v >= ULong.MAX_VALUE.toDouble() -> ULong.MAX_VALUE
|
||||
v < Long.MAX_VALUE -> v.toLong().toULong()
|
||||
|
||||
// Real values from Long.MAX_VALUE to (Long.MAX_VALUE + 1) are not representable in Double, so don't handle them.
|
||||
else -> (v - 9223372036854775808.0).toLong().toULong() + 9223372036854775808uL // Long.MAX_VALUE + 1 < v < ULong.MAX_VALUE
|
||||
}
|
||||
|
||||
|
||||
@PublishedApi
|
||||
internal fun uintToDouble(v: Int): Double = (v and Int.MAX_VALUE).toDouble() + (v ushr 31 shl 30).toDouble() * 2
|
||||
|
||||
@PublishedApi
|
||||
internal fun ulongToDouble(v: Long): Double = (v ushr 11).toDouble() * 2048 + (v and 2047)
|
||||
|
||||
|
||||
internal fun ulongToString(v: Long): String = ulongToString(v, 10)
|
||||
|
||||
internal fun ulongToString(v: Long, base: Int): String {
|
||||
|
||||
Reference in New Issue
Block a user