How to get an unsigned integer from a byte array in Kotlin JVM?
Asked Answered
I

4

12

Kotlin 1.3 introduced unsigned integer types, but I can't seem to figure out how to get an unsigned integer from a ByteArray in Kotlin JVM.

Kotlin Native has a convenient ByteArray.getUIntAt() method, but this does not exist for Kotlin JVM.

val bytes: ByteArray = byteArrayOf(1, 1, 1, 1)
val uint: UInt // = ???

What are my options here? Is there a more elegant way than using a ByteBuffer, or bit-shifting my way out of this?

Indorse answered 26/12, 2018 at 7:46 Comment(8)
you can use val uint = bytes[index].toUInt()Cannon
@Cannon No, that just converts the single byte at index. I need to convert the 4 bytes to a single unsigned integer.Indorse
but what's logic .. all elements should add or what ??Cannon
getUIntAt() also accept the index which does the sameCannon
@Cannon There's no logic. A UInt is a 32 bit signed integer. 32 bits is 4 bytes.Indorse
@Cannon I assume that getUIntAt(index) reads the 4 bytes starting at index and returns a UInt of which the value corresponds to those 4 bytes.Indorse
@Cannon Just verified the behavior on Kotlin Native and confirmed that the behavior is as described in my previous comment. For the byte array in my question, calling bytes.getUIntAt(0) returns 16843009, which is correct.Indorse
ok then kotlin JVM does not have any method .. What you can do is use ByteBuffer and create extension function to make it reusable ..Cannon
A
18

As mentioned in the comments there is no out of the box solution in the JVM version of Kotlin. An extension function doing the same as the Kotlin/Native function might look like this:

fun ByteArray.getUIntAt(idx: Int) =
    ((this[idx].toUInt() and 0xFFu) shl 24) or
            ((this[idx + 1].toUInt() and 0xFFu) shl 16) or
            ((this[idx + 2].toUInt() and 0xFFu) shl 8) or
            (this[idx + 3].toUInt() and 0xFFu)

fun main(args: Array<String>) {

    // 16843009
    println(byteArrayOf(1, 1, 1, 1).getUIntAt(0))

    // 4294967295, which is UInt.MAX_VALUE
    println(byteArrayOf(-1, -1, -1, -1).getUIntAt(0))
}
Alialia answered 26/12, 2018 at 10:35 Comment(0)
S
2

Shocked that there is no such library yet, I've created https://github.com/mvysny/kotlin-unsigned-jvm . It's in Maven Central, please take a look. Adds extension functions for ByteArray and supports both Endian.Big and Endian.Little.

Stereopticon answered 14/4, 2023 at 8:45 Comment(0)
T
1

To expand upon @Alexander's excellent answer, I wrote a version that accepts ByteArrays that have fewer than four values. I've found this particularly useful when parsing ByteArrays with a mix of signed and unsigned integers (in my case bluetooth characteristic update notifications) that I need to slice into sub-arrays.

fun ByteArray.fromUnsignedBytesToInt(): Int {
//Note: UInt is always 32 bits (4 bytes) regardless of platform architecture
//See https://kotlinlang.org/docs/basic-types.html#unsigned-integers
    val bytes = 4
    val paddedArray = ByteArray(bytes)
    for (i in 0 until bytes-this.size) paddedArray[i] = 0
    for (i in bytes-this.size until paddedArray.size) paddedArray[i] = this[i-(bytes-this.size)]

    return (((paddedArray[0].toULong() and 0xFFu) shl 24) or
            ((paddedArray[1].toULong() and 0xFFu) shl 16) or
            ((paddedArray[2].toULong() and 0xFFu) shl 8) or
            (paddedArray[3].toULong() and 0xFFu)).toInt()
}

The test class below confirms the expected values:

import org.junit.Test
import org.junit.Assert.*

internal class ByteArrayExtKtTest {
    @Test
    fun testAsUnsignedToIntTwoBytes() {
        val bytes = byteArrayOf (0x0A.toByte(), 0xBA.toByte())
        assertEquals(2746, bytes.fromUnsignedBytesToInt())
    }

    @Test
    fun testAsUnsignedToIntFourBytes() {
        val bytes = byteArrayOf(1,1,1,1)
        assertEquals(16843009, bytes.fromUnsignedBytesToInt())
    }
}
Tamarind answered 5/1, 2022 at 15:6 Comment(0)
E
0

Great answers here. Here is a solution that can take a byte array of any size and do the proper conversion (I used the above answers as a starting point)

fun ByteArray.toUInt(): UInt {
    //Note: UInt is always 32 bits (4 bytes) regardless of platform architecture
    //See https://kotlinlang.org/docs/basic-types.html#unsigned-integers
    val bytes = 4
    val paddedArray = ByteArray(bytes) { 0 }

    val offset = (size-bytes)
    for (i in (size-1).coerceAtLeast(0) downTo offset.coerceAtLeast(0)) {
        paddedArray[i-offset] = this[i]
    }

    return ((paddedArray[0].toUInt() and 0xFFu) shl 24) or
            ((paddedArray[1].toUInt() and 0xFFu) shl 16) or
            ((paddedArray[2].toUInt() and 0xFFu) shl 8) or
            (paddedArray[3].toUInt()  and 0xFFu)
}
Echoechoic answered 24/2, 2023 at 16:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.