1. Introduction
Yes, we can do bitwise operations in Kotlin, too. Despite the fact that the usual symbols, like <<*, *>>, |, &, and ^, are missing, we can do everything we can in C, but with functions of classes Int and Long.
The Kotlin community has been arguing about bitwise operators for ten years. Those who support the idea say that it’s a requirement of their low-level domains – usually, sound or video processing. Those who oppose it put other things as more important and also point out that sometimes the code overcrowded with bitwise symbols is completely unreadable in languages that allow them.
For now, let’s review how we can do bitwise operations in Kotlin with functions.
2. Kotlin Bitwise Operations and Their Java Counterparts
Most of us have encountered bitwise operations before. Let’s refresh our memory and compare Kotlin and Java syntax:
Operation Name
Java Operator
Kotlin Int/Long Function
Conjunction (and)
a & b
a and b
Disjunction (or)
a | b
a or b
Exclusive disjunction (xor)
a ^ b
a xor b
Inversion
~ a
a.inv()
Shift Left
a << bits
a shl bits
Shift Right
a >> bits
a shr bits
Unsigned Shift Right
a >>> bits
a ushr bits
Java also includes assignment operators modified with each of the bitwise operators, like |=. In Kotlin, we will have to repeat ourselves: a = a or b.
3. How Bitwise Operations Work
Now that we know how to write bitwise operations, let’s discuss what they do with their operands.
3.1. Conjunction, Disjunction, and Inversion
Bitwise conjunction, disjunction, and inversion work similarly to their logical counterparts, but they affect each bit of their operands separately. So, for bitwise conjunction, that would mean:
val a = 0b10011 // 19
val b = 0b11110 // 30
assert(a and b == 0b10010) // 18
Here, we used a way of recording numerical literals in the binary notation for clarity.
And for the disjunction:
val a = 0b101001 // 41
val b = 0b110011 // 51
assert(a or b == 0b111011) // 59
For the exclusive disjunction, we will get 0 if the corresponding operand bits are both 0 or both 1:
val a = 0b110101
val b = 0b101010
assert(a xor b == 31) // 11111
And the inversion is the simplest of all – all 0 become 1 and vice versa. However, we have to remember that the Int type has 32 bytes and Long has 64. That means, to get correct results for shorter binary numbers, we have to mask them with and:
assert(0b101100.inv() and 0b111111 == 0b010011)
3.2. Binary Shifts
Binary shifts are exactly that: We take a binary number and shift it by the specified number of positions to the left or to the right:
assert(0b110011 shl 2 == 0b11001100)
There is only one left shift, but we have two right shifts: signed and unsigned. The unsigned right shift copies zeros to the left of the number and drops the rightmost bits. The result of an unsigned shift is always positive, even if we shift a negative number:
assert(-0b1100110011 ushr 22 == 0b1111111111)
The signed right shift, however, copies whatever bit happens to be the leftmost in the number. It means that the sign of the number will stay the same:
assert(0b1100110011 shr 2 == 0b11001100) // the first 22 bytes are zeroes
assert(0b11111111111111111111110011001101 shr 2 == 0b111111111111111111111100110011)
// the first bit is 1, it means that the number is negative
4. Applications of Bitwise Operations
So, why do we need these operations anyway? They’re really basic, and on most CPUs, they’ll take only one cycle to compute. The right shift represents integer division by 2bits, while the left shift is multiplication by 2bits. Let’s see them in action:
assert(12 shr 2 == 3) // 12 / 2^2 == 12 / 4
assert(3 shl 3 == 24) // 3 * 2^3 == 3 * 8
Unlike bitwise operators, actual multiplication and division might take more than one cycle.
Then, there’s the ability to pack information really tightly, each bit meaning something specific. Then we can use a mask and an and operator to check for this specific property:
val SKY_IS_BLUE_MASK = 0b00000000000001000000000000
fun isSkyBlue(worldProperties: Int): Boolean =
worldProperties and SKY_IS_BLUE_MASK != 0
assert(isSkyBlue(0b10011100111101011101010101))
With or, we can combine various flags together:
val SKY_IS_BLUE = 0b00000000000001000000000000
val SUN_IS_SHINING = 0b00000000000000100000000000
val skyIsBlueAndSunShines = SKY_IS_BLUE or SUN_IS_SHINING // 0b00000000000001100000000000
5. Conclusion
Bitwise operations are available in Kotlin, just as they are in other major languages, like C/C++, Python, and Java. They are implemented as infix functions of Int and Long types except for the inversion inv(). Bitwise operations are useful in signal processing, high-performance calculations, and creating tightly-packed data structures.
As usual, all the examples are available over on GitHub.