Adding bits conversion from small to large integers

The Kotlin language has a two different way to handle integer transformations.

If I try to transform an Int to a Short, only the Short bits from the Int are taken and the value can become negative:

65535.toShort() == (-1).toShort() // 0xFFFF

However the other way is not possible, as the actual value is taken to transform the Short to become an Int.

-1 == (-1).toShort().toInt() // == 0xFFFF_FFFF

I’ve made some extension function to counter this behavior.

fun Byte.bitsToShort(): Short = if (this < 0) (0xFF + toInt() + 1).toShort() else toShort()
fun Byte.bitsToInt(): Int = if (this < 0) 0xFF + toInt() + 1 else toInt()
fun Byte.bitsToLong(): Long = if (this < 0) 0xFFL + toLong() + 1L else toLong()
fun Short.bitsToInt(): Int = if (this < 0) 0xFFFF + toInt() + 1 else toInt()
fun Short.bitsToLong(): Long = if (this < 0) 0xFFFFL + toLong() + 1L else toLong()
fun Int.bitsToLong(): Long = if (this < 0) 0xFFFFFFFFL + toLong() + 1L else toLong()

I know that I can use (-1).toShort().toUShort().toInt() but its an extra step that creates a value class under the hood. I don’t know the cost of doing it this way, but it doesn’t feel right. Unsigned values are still experimental too.

Is there a better way or native way to do that with kotlin ? I’d love to see something like this be in the language itself or in the sdk.

Usual way in many languages is:

fun Short.bitsToInt(): Int = toInt() and 0xffff

But I think toUShort().toInt() is also fine and internally it does a similar thing to above. Also, the question is why do you store value 65535 in a signed short in the first place. It feels like it should be an UShort from the beginning, and then simply toInt() feels much more natural.

3 Likes

@broot Thank you for the answer.

For context, I work with KMP and it is because I receive a stream of Byte which every item is in fact a UByte. I didn’t want to take a performance hit on this part by doing conversions.

I’m glad I asked such a simple question.

I believe Byte.toUByte() is a no-op practically

I believe the same. Technically, it calls the “constructor” of UByte, but we don’t expect any real conversion, so I guess it simply returns the same value. Then toInt does the same as I did above. So I suspect in practice it is exactly the same and it is probably the optimal way performance-wise.