Skip to main content

Kotlin Unsigned Types

Introduction

Kotlin, like many modern programming languages, provides signed numeric types by default. However, sometimes we need to work with data that is inherently non-negative, such as memory addresses, array indices, or network protocol fields. For these cases, Kotlin introduced unsigned integer types starting from version 1.3.

Unsigned types can represent only non-negative integers, but with a higher positive range compared to their signed counterparts. This feature extends Kotlin's type system and provides a more accurate way to express certain programming concepts.

Understanding Unsigned Types

What Are Unsigned Types?

Unsigned types are integer types that can only store non-negative values (zero and positive numbers). By removing the need to represent negative values, they can use the entire bit range for positive numbers, effectively doubling the maximum value that can be stored.

Available Unsigned Types in Kotlin

Kotlin provides four unsigned integer types:

TypeSize (bits)Min ValueMax Value
UByte80255 (2^8 - 1)
UShort16065,535 (2^16 - 1)
UInt3204,294,967,295 (2^32 - 1)
ULong64018,446,744,073,709,551,615 (2^64 - 1)

Declaring Unsigned Variables

You can declare unsigned variables using the type name directly or by using the u and U suffixes for literal values:

kotlin
// Using type declaration
val myUByte: UByte = 10u
val myUShort: UShort = 1000u
val myUInt: UInt = 1000000u
val myULong: ULong = 1000000000000UL

// Using literals with suffixes
val a = 42u // UInt: Type inferred from the u suffix
val b = 42UL // ULong: Type specified with UL suffix
val c = 0xFFFF_FFFF_FFFFu // ULong: Hexadecimal literal

The suffix u or U creates an unsigned integer literal. By default, such literals have UInt or ULong type depending on the size of the value. You can specify the exact type using UL for ULong, or by explicitly providing the type:

kotlin
val smallUByte: UByte = 255u  // OK: 255 fits in UByte
val tooBigForByte: UByte = 256u // Error: 256 doesn't fit in UByte

Converting Between Signed and Unsigned Types

Kotlin provides extension functions to convert between signed and unsigned types:

kotlin
// From signed to unsigned
val signedInt = 10
val unsignedInt = signedInt.toUInt() // 10u

// From unsigned to signed
val unsignedLong = 42UL
val signedLong = unsignedLong.toLong() // 42L

// Converting between different unsigned types
val uint = 300u
val ubyte: UByte = uint.toUByte() // Truncates to 44 (300 % 256)

Example: Working with Unsigned Types

Let's see how unsigned types can be used in practice:

kotlin
fun main() {
// Using unsigned integers for array size
val arraySize = 10u
val array = UIntArray(arraySize.toInt()) { (it * 2).toUInt() }

println("Array elements: ${array.joinToString()}")

// Demonstrating the higher positive range
val maxSignedByte: Byte = 127 // Maximum value for signed Byte
val maxUnsignedByte: UByte = 255u // Maximum value for unsigned Byte

println("Max signed byte: $maxSignedByte")
println("Max unsigned byte: $maxUnsignedByte")

// Wrapping behavior
val justBeforeWrap: UByte = 255u
val afterWrap: UByte = (justBeforeWrap + 1u).toUByte()

println("Before wrap: $justBeforeWrap")
println("After wrap: $afterWrap") // Outputs 0
}

Output:

Array elements: 0, 2, 4, 6, 8, 10, 12, 14, 16, 18
Max signed byte: 127
Max unsigned byte: 255
Before wrap: 255
After wrap: 0

Unsigned Arrays

Kotlin also provides specialized array classes for unsigned types:

kotlin
// Creating unsigned arrays
val ubyteArray = UByteArray(5) { (it * 2).toUByte() }
val ushortArray = UShortArray(5) { (it * 10).toUShort() }
val uintArray = UIntArray(5) { it.toUInt() }
val ulongArray = ULongArray(5) { it.toULong() }

// Accessing elements
println(ubyteArray[3]) // Outputs 6u

Practical Applications of Unsigned Types

1. Network Programming

Unsigned types are perfect for network protocols where fields are often specified as unsigned values:

kotlin
fun parseIpAddress(bytes: UByteArray): String {
require(bytes.size == 4) { "IPv4 address must be 4 bytes" }
return bytes.joinToString(".") { it.toString() }
}

fun main() {
val ipAddressBytes = ubyteArrayOf(192u, 168u, 0u, 1u)
val ipAddress = parseIpAddress(ipAddressBytes)
println("IP Address: $ipAddress")
}

Output:

IP Address: 192.168.0.1

2. Working with Binary Data

When reading binary data from files or streams, unsigned types can provide a more accurate representation:

kotlin
fun readFileHeader(bytes: UByteArray): String {
// Common file formats often use unsigned values for headers
val fileType = when {
bytes.size >= 4 && bytes[0] == 0x89u && bytes[1] == 0x50u &&
bytes[2] == 0x4Eu && bytes[3] == 0x47u -> "PNG"
bytes.size >= 2 && bytes[0] == 0xFFu && bytes[1] == 0xD8u -> "JPEG"
else -> "Unknown"
}
return "File type: $fileType"
}

fun main() {
val pngHeader = ubyteArrayOf(0x89u, 0x50u, 0x4Eu, 0x47u)
println(readFileHeader(pngHeader))
}

Output:

File type: PNG

3. Memory Management

When dealing with memory addresses or sizes, unsigned types provide a more natural fit:

kotlin
fun allocateMemory(sizeInBytes: UInt): MemoryBlock {
println("Allocating $sizeInBytes bytes of memory")
// Simulation of memory allocation
return MemoryBlock(sizeInBytes)
}

class MemoryBlock(val size: UInt) {
override fun toString() = "Memory block of $size bytes"
}

fun main() {
val blockSize = 1024u * 1024u // 1 MB in UInt
val memoryBlock = allocateMemory(blockSize)
println(memoryBlock)
}

Output:

Allocating 1048576 bytes of memory
Memory block of 1048576 bytes

Limitations and Considerations

While unsigned types are useful, they come with some limitations and considerations:

  1. Java Interoperability: Unsigned types are Kotlin-specific and don't have direct equivalents in Java.

  2. API Coverage: Not all standard library functions support unsigned types yet.

  3. Type Inference: Be careful with type inference, as simple operations might change the resulting type:

kotlin
val a = 1u + 2 // Results in UInt (3u)
val b = 1 + 2u // Results in UInt (3u)

val x = 1u.toUByte() + 2u.toUByte() // Results in UInt (3u), not UByte!
val y: UByte = (1u.toUByte() + 2u.toUByte()).toUByte() // Explicit conversion needed

Summary

Kotlin's unsigned types provide a way to represent non-negative integers with a higher positive range compared to their signed counterparts. They are particularly useful in scenarios involving:

  • Network programming and protocols
  • Binary data parsing and manipulation
  • Memory management and addressing
  • Performance-critical code where the extra range is needed

Unsigned types are still an experimental feature in Kotlin, but they're stabilizing quickly and becoming more integrated with the language ecosystem.

Additional Resources

Exercises

  1. Create a function that converts an IP address in string format (like "192.168.0.1") to a UByteArray.

  2. Implement a simple checksum calculator using UInt that adds up all bytes in a UByteArray.

  3. Create a UInt extension function to check if a specific bit is set (equals 1) at a given position.

  4. Implement a hexadecimal color parser that converts a string like "#FF00A3" to a set of UByte color components.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)