How does arithmetic, comparison, and overflow behave for Kotlin unsigned types, and where do mixed signed/unsigned operations fail?
answer
- Same-type only: 1u + 1 won't compile
- Overflow wraps mod 2^n, never throws
- Unsigned divide/compare: big/2u is positive, big>1u true
- shr is logical; no ushr for unsigned
- No unary minus; MIN_VALUE is 0; ranges work
basics
~20 sUnsigned arithmetic wraps around silently on overflow, never throwing. Division and comparison use unsigned rules, so big values aren't treated as negative. You cannot freely mix signed and unsigned in one expression; you must convert first.
solid answer
~40 sUnsigned operators (`+`, `-`, `*`, `/`, `%`) are defined only between the same unsigned types and use **unsigned semantics**: overflow **wraps modulo 2^n** silently (like signed Int, no exception), but division and comparison treat the operands as non-negative, so `4294967295u / 2u` is `2147483647u`, not a negative result. Comparisons via `<`, `compareTo`, and ranges are unsigned-ordered. You **cannot mix** signed and unsigned in arithmetic — `1u + 1` doesn't compile; you must convert one side (`1u + 1.toUInt()`). Bitwise ops (`and`, `or`, `xor`, `inv`, `shl`, `shr`) exist; note unsigned has only logical `shr` (no separate `ushr`, since it's already unsigned). Ranges and progressions work: `0u until 10u`, `(0u..5u)`. There's no unary minus, and `MIN_VALUE` is always 0. Idiomatic guidance: keep unsigned local to bit/protocol code.
code
kotlin · 5 linesprintln(UInt.MAX_VALUE + 1u) // 0u (wrap)
println(0u - 1u) // 4294967295u (wrap)
println(4_294_967_295u / 2u) // 2147483647u (unsigned divide)
println((0xFF00u) shr 8) // 255u (logical shift)
// val nope = 1u + 1 // compile error: UInt + Intgo deeper
Knows unsigned values stay non-negative and can't be freely mixed with Int.
Explains silent wraparound and that division/comparison use unsigned semantics.
Articulates the no-ushr/no-unary-minus details, range support, and the mixed-type compile barrier with conversions.
Reasons about the absence of checked arithmetic, where unsigned belongs in an API, and the cost of converting at boundaries.
## Operators are unsigned-only and same-type Arithmetic operators (`+ - * / %`) for unsigned types are defined **between the same unsigned type** (e.g. `UInt op UInt`). Kotlin does **not** auto-promote, so a mixed expression like: ```kotlin val bad = 1u + 1 // does NOT compile: UInt + Int undefined val ok = 1u + 1u // 2u val ok2 = 1u + 1.toUInt() ``` ## Overflow wraps silently Like signed Int arithmetic in Kotlin, unsigned arithmetic **wraps modulo 2^n** and never throws on overflow: ```kotlin println(UInt.MAX_VALUE + 1u) // 0u (wraps) println(0u - 1u) // 4294967295u (wraps to max) ``` There is **no unary minus** for unsigned types and `MIN_VALUE` is always `0`. `0u - 1u` is valid subtraction (wrap), but `-(1u)` is not a defined operator. ## Division, modulo, and comparison use unsigned rules This is the whole point of unsigned types. The same bit patterns that would be negative as signed are treated as large positives: ```kotlin val big = 4_294_967_295u println(big / 2u) // 2147483647u (unsigned divide) println(big > 1u) // true (unsigned compare; as Int it'd be -1 < 1) ``` Under the hood these route to `Integer.divideUnsigned`, `remainderUnsigned`, and `compareUnsigned`. ## Bitwise operations Unsigned types support infix `and`, `or`, `xor`, `inv()`, and shifts `shl` / `shr`. Because the value is already unsigned, **`shr` is logical** (zero-fill) and there is **no separate `ushr`** — unlike signed Int, which has both arithmetic `shr` and unsigned `ushr`. ```kotlin val m = 0xFF00u println(m shr 8) // 0x00FFu, zero-filled println(m and 0x0F00u) ``` ## Ranges and progressions Unsigned ranges work like signed: `UIntRange`, `ULongRange`, plus `until`, `..`, `downTo`, `step`: ```kotlin for (i in 0u until 4u) print(i) // 0123 ``` ## Practical pitfalls - Silent wrap can hide bugs — there's no checked-arithmetic variant. - Mixed signed/unsigned won't compile; converting at the boundary is required and easy to get wrong (see byte sign-extension). - Java interop and tooling friction (mangling) push teams to keep unsigned **internal** to bit/hash/protocol code rather than across public APIs. ## Summary Unsigned arithmetic = same-type only, silent modular wrap, genuine unsigned divide/compare/shift, ranges supported, no unary minus, logical-only `shr`.
- Why doesn't unsigned have a separate ushr operator?Because the value is already unsigned, its shr is already a logical (zero-fill) shift. Signed Int needs both arithmetic shr and unsigned ushr; unsigned only needs the logical one.
- What happens on UInt.MAX_VALUE + 1u?It wraps to 0u silently. Unsigned arithmetic is modular and never throws on overflow; there's no checked variant.
saying these in an interview costs you the question
- Expecting an exception on unsigned overflow
- Thinking 1u + 1 compiles via automatic promotion
- Believing unsigned division can yield a negative result
- Looking for ushr on UInt or expecting unary minus to work