Bitwise operators allow you to manipulate individual bits within binary representations of integers, making them an essential tool for low-level programming, optimization, and systems that require direct memory manipulation. While not as commonly used in high-level application development, understanding bitwise operations is crucial for working with data structures, cryptography, hardware interfacing, and other performance-sensitive applications.
In this article, we’ll explore the various bitwise operators available in Kotlin, how they work, and practical examples of their use. By the end, you’ll have a solid grasp of how to perform efficient low-level operations using bitwise operators.
What Are Bitwise Operators?
Bitwise operators work directly on the binary representation of integers. Each operator performs its operation on the bits that make up the integer value. Kotlin provides the following bitwise operators for manipulating bits:
- AND (
and
) - OR (
or
) - XOR (
xor
) - NOT (
inv
) - Shift Left (
shl
) - Shift Right (
shr
) - Unsigned Shift Right (
ushr
)
Let’s break down each of these operators and see how they work with examples.
1. Bitwise AND (and
)
The bitwise AND operator compares each bit of two integers. It returns 1
for each bit position where both corresponding bits are 1
; otherwise, it returns 0
.
Example:
fun main() {
val a = 5 // Binary: 0101
val b = 3 // Binary: 0011
val result = a and b // Result: 0001 (1 in decimal)
println("a AND b: $result") // Output: 1
}
How it Works:
0101 (a)
& 0011 (b)
--------
0001 (Result)
2. Bitwise OR (or
)
The bitwise OR operator compares each bit of two integers. It returns 1
if at least one of the corresponding bits is 1
; otherwise, it returns 0
.
Example:
fun main() {
val a = 5 // Binary: 0101
val b = 3 // Binary: 0011
val result = a or b // Result: 0111 (7 in decimal)
println("a OR b: $result") // Output: 7
}
How it Works:
0101 (a)
| 0011 (b)
--------
0111 (Result)
3. Bitwise XOR (xor
)
The bitwise XOR (exclusive OR) operator returns 1
when the corresponding bits of the two operands are different. If the bits are the same, it returns 0
.
Example:
fun main() {
val a = 5 // Binary: 0101
val b = 3 // Binary: 0011
val result = a xor b // Result: 0110 (6 in decimal)
println("a XOR b: $result") // Output: 6
}
How it Works:
0101 (a)
^ 0011 (b)
--------
0110 (Result)
4. Bitwise NOT (inv
)
The bitwise NOT operator (inv
) inverts each bit of an integer, flipping 0
s to 1
s and 1
s to 0
s. In Kotlin, this is a unary operator that applies only to a single number.
Example:
fun main() {
val a = 5 // Binary: 0101
val result = a.inv() // Result: 1010 (Inverted binary, but shown as negative in decimal)
println("NOT a: $result") // Output: -6
}
How it Works:
0101 (a)
------
1010 (Result)
However, in Kotlin, numbers are stored using two’s complement representation, which is why the result of inv()
is shown as -6
in decimal.
5. Shift Left (shl
)
The shift left operator (shl
) shifts all bits of the number to the left by a specified number of positions, filling the rightmost bits with 0
s. This effectively multiplies the number by powers of 2.
Example:
fun main() {
val a = 5 // Binary: 0101
val result = a shl 1 // Shift left by 1 position (equivalent to 5 * 2)
println("a shifted left: $result") // Output: 10
}
How it Works:
0101 (a)
<< 1
--------
1010 (Result: 10 in decimal)
6. Shift Right (shr
)
The shift right operator (shr
) shifts all bits of the number to the right by a specified number of positions. This operator preserves the sign bit (for negative numbers), meaning it maintains the number’s sign when shifting.
Example:
fun main() {
val a = 16 // Binary: 10000
val result = a shr 1 // Shift right by 1 position (equivalent to 16 / 2)
println("a shifted right: $result") // Output: 8
}
How it Works:
10000 (a)
>> 1
--------
01000 (Result: 8 in decimal)
7. Unsigned Shift Right (ushr
)
The unsigned shift right operator (ushr
) shifts the bits to the right by the specified number of positions, but unlike shr
, it fills the leftmost bits with 0
s, regardless of whether the number is positive or negative. This operator is typically used for handling unsigned values.
Example:
fun main() {
val a = -16
val result = a ushr 1
println("a unsigned shifted right: $result") // Output: 2147483640 (in Kotlin's two's complement)
}
Real-World Use Case: Setting and Clearing Flags
A common use of bitwise operations is for setting and clearing flags. Let’s say you are working on a permissions system where each bit represents a specific permission (e.g., read, write, execute).
Example:
fun main() {
val READ = 1 // 0001
val WRITE = 2 // 0010
val EXECUTE = 4 // 0100
var permissions = READ or WRITE // Grant read and write permissions
// Check if WRITE permission is granted
if (permissions and WRITE != 0) {
println("Write permission granted")
}
// Revoke WRITE permission
permissions = permissions and WRITE.inv()
// Check if WRITE permission is still granted
if (permissions and WRITE == 0) {
println("Write permission revoked")
}
}
In this example, we use bitwise operators to grant, check, and revoke permissions.
Conclusion
Bitwise operators in Kotlin provide powerful tools for low-level data manipulation. While they may not be frequently used in high-level application development, they are essential in situations where you need to work directly with binary data, such as cryptography, hardware interfacing, and performance optimization.
- Bitwise AND (
and
): Compares bits, returning1
only where both bits are1
. - Bitwise OR (
or
): Compares bits, returning1
if at least one bit is1
. - Bitwise XOR (
xor
): Compares bits, returning1
if the bits differ. - Bitwise NOT (
inv
): Inverts the bits, flipping0
to1
and1
to0
. - Shift Left (
shl
): Shifts bits to the left, effectively multiplying the number. - Shift Right (
shr
): Shifts bits to the right, preserving the sign bit. - Unsigned Shift Right (
ushr
): Shifts bits to the right, filling left bits with0
s.
Next up, we’ll explore assignment operators in Kotlin and see how they can simplify your code by combining arithmetic and assignment operations. Stay tuned!