Implement conversion between floating point numbers and unsigned integers

KT-27108
This commit is contained in:
Abduqodiri Qurbonzoda
2019-01-28 14:49:23 +03:00
committed by Ilya Gorbunov
parent a970de51ab
commit 1b6b44c805
10 changed files with 364 additions and 1 deletions
+1
View File
@@ -34,6 +34,7 @@ enum class PrimitiveType(val byteSize: Int?) {
companion object {
val exceptBoolean = PrimitiveType.values().filterNot { it == BOOLEAN }
val onlyNumeric = PrimitiveType.values().filterNot { it == BOOLEAN || it == CHAR }
val floatingPoint = listOf(FLOAT, DOUBLE)
}
}
+28
View File
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.generators.builtins.unsigned
import org.jetbrains.kotlin.generators.builtins.PrimitiveType
import org.jetbrains.kotlin.generators.builtins.UnsignedType
import org.jetbrains.kotlin.generators.builtins.convert
import org.jetbrains.kotlin.generators.builtins.generateBuiltIns.BuiltInsSourceGenerator
@@ -81,6 +82,7 @@ class UnsignedTypeGenerator(val type: UnsignedType, out: PrintWriter) : BuiltIns
generateBitwiseOperators()
generateMemberConversions()
generateFloatingConversions()
generateToStringHashCode()
@@ -223,6 +225,21 @@ class UnsignedTypeGenerator(val type: UnsignedType, out: PrintWriter) : BuiltIns
out.println()
}
private fun generateFloatingConversions() {
for (otherType in PrimitiveType.floatingPoint) {
val otherName = otherType.capitalized
out.println(" @kotlin.internal.InlineOnly")
out.print(" public inline fun to$otherName(): $otherName = ")
when (type) {
UnsignedType.UINT, UnsignedType.ULONG ->
out.println(if (otherType == PrimitiveType.FLOAT) "this.toDouble().toFloat()" else className.toLowerCase() + "ToDouble(data)")
else ->
out.println("this.toInt().to$otherName()")
}
}
out.println()
}
private fun generateExtensionConversions() {
for (otherType in UnsignedType.values()) {
val otherSigned = otherType.asSigned.capitalized
@@ -236,6 +253,17 @@ class UnsignedTypeGenerator(val type: UnsignedType, out: PrintWriter) : BuiltIns
else -> "$className(this.to$thisSigned())"
})
}
out.println()
for (otherType in PrimitiveType.floatingPoint) {
val otherName = otherType.capitalized
out.println("@SinceKotlin(\"1.3\")")
out.println("@ExperimentalUnsignedTypes")
out.println("@kotlin.internal.InlineOnly")
out.print("public inline fun $otherName.to$className(): $className = ")
val conversion = if (otherType == PrimitiveType.DOUBLE) "" else ".toDouble()"
out.println("doubleTo$className(this$conversion)")
}
}
@@ -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())
}
}
}
+141 -1
View File
@@ -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 {
@@ -459,12 +459,18 @@ public abstract interface annotation class kotlin/UnsafeVariance : java/lang/ann
}
public final class kotlin/UnsignedKt {
public static final fun doubleToUByte (D)B
public static final fun doubleToUInt (D)I
public static final fun doubleToULong (D)J
public static final fun doubleToUShort (D)S
public static final fun uintCompare (II)I
public static final fun uintDivide-J1ME1BU (II)I
public static final fun uintRemainder-J1ME1BU (II)I
public static final fun uintToDouble (I)D
public static final fun ulongCompare (JJ)I
public static final fun ulongDivide-eb3DHEI (JJ)J
public static final fun ulongRemainder-eb3DHEI (JJ)J
public static final fun ulongToDouble (J)D
}
public abstract interface annotation class kotlin/UseExperimental : java/lang/annotation/Annotation {