1. Overview

In programming, bit operations are low-level operations used to manipulate individual bits in a value stored in a computer’s memory. These operations include bitwise operations, shifts, and bitmasks.

In this article, we’ll look at several examples of integer bit manipulation functions provided by Kotlin.

2. Logical Bit Operations

Logical bitwise operators evaluate two integers in binary form. They compare the bits at corresponding positions and then compute a new integer value based on the comparison.

2.1. and Operator

The most basic bitwise operation is the “and” operator (and), which compares each bit of the first integer to the corresponding bit of the second integer. If both bits are 1, the corresponding result bit is set to 1. Otherwise, the result bit is set to 0:

@Test
fun `applies and bit operator on two integers`() {
    val a = 0b00011110
    val b = 0b01111000
    assertEquals(0b00011000, a and b)
}

2.2. or Operator

Kotlin also provides the “or” operator (or), which compares each bit of the first integer to the corresponding bit of the second integer. If either bit is 1, the corresponding result bit is set to 1. Otherwise, the result bit is set to 0:

@Test
fun `applies or bit operator on two integers`() {
    val a = 0b00011110
    val b = 0b01111000
    assertEquals(0b01111110, a or b)
}

2.3. xor Operator

Kotlin also provides the “xor” operator (xor), which performs a bitwise “exclusive or” operation on two integers. This operation compares each bit of the first integer to the corresponding bit of the second integer. If the bits are different, the corresponding result bit is set to 1. Otherwise, the result bit is set to 0:

@Test
fun `applies xor bit operator on two integers`() {
    val a = 0b00011110
    val b = 0b01111000
    assertEquals(0b01100110, a xor b)
}

3. Bit Shift Operations

Kotlin provides several functions for shifting the bits of an integer value to the left or right. These shifts are often used to multiply or divide an integer by a power of two.

3.1. Signed and Unsigned Numbers

Kotlin allows bit shifts on both signed and unsigned integers, so before diving in, let’s quickly explore the difference between these two types of integer representations. The representation of a signed binary number is commonly referred to as the sign-magnitude notation. If the sign bit is “0”, the number is treated as positive. If the sign bit is “1”, then the number is negative. Sign magnitude is represented by the leftmost bit, called the most significant bit, of a binary representation of a number.

3.2. Left Shift (shl) Operator

The “shift left” operator (shl) shifts the bits of an integer to the left by a specified number of places, preserving the original sign of the integer. The least significant bits on the right side of the value are filled with zeros. This operation is equivalent to multiplying the integer by n^2:

@Test
fun `shifts bits left in an integer`() {
    assertEquals(512, 128 shl 2)
}

3.3. Right Shift (shr) Operator

The “shift right” operator (shr) shifts the bits of an integer to the right by a specified number of places. This operation is equivalent to dividing the integer by n^2, preserving the sign of the original integer:

@Test
fun `shifts bits right in a positive integer`() {
    assertEquals(32, 128 shr 2)
}
@Test
fun `shifts bits right in a negative integer`() {
    val a = -128 // 11111111111111111111111110000000
    val expected = -32 // 11111111111111111111111111100000
    assertEquals(expected, a shr 2)
}

3.4. Unsigned Right Shift (ushr) Operator

Kotlin also provides the “unsigned shift right” operator (ushr), which shifts the bits of an integer to the right by a specified number of places. Excess bits shifted to the right are discarded and zero bits are always shifted in from the left. Contrary to the shr operator, the ushr operator does not preserve the sign of the original integer:

@Test
fun `shifts unsigned bits right in an integer`() {
    val a = -128 // 11111111111111111111111110000000
    val expected = 1073741792 // 00111111111111111111111111100000
    assertEquals(expected, a ushr 2)
}

4. Bit Methods

Bitmasks are used to isolate or modify specific bits within an integer value. Since version 1.4.0, Kotlin provides several functions for creating and manipulating bitmasks.

These functions can be helpful for working with binary data or optimizing code for performance. They provide additional tools for manipulating and analyzing the bits of an integer value.

For brevity, we’ll be using the UByte integer type in our examples. It stores 8 bits and holds an unsigned integer in the range of 0 to 255. Converting from any integer type to a UByte type takes the 8 least significant bits of the integer.

4.1. Inverse Bits: the inv() Method

The inverse bits method inv() returns the bitwise inverse of an integer. This function flips all the bits of the integer, changing all the 1s to 0s and all the 0s to 1s:

@Test
fun `inverts bits in an integer`() {
    val a = 0b00011110.toUByte()
    assertEquals(0b11100001.toUByte(), a.inv())
}

4.2. countOneBits() Method

The countOneBits() method returns the number of 1 bits in the binary representation of an integer:

@Test
fun `counts one bits in an integer`() {
    val a = 0b00111110.toUByte()
    assertEquals(5, a.countOneBits())
}

4.3. countLeadingZeroBits() and countTrailingZeroBits() Methods

The countLeadingZeroBits() and countTrailingZeroBits() methods return the number of leading or trailing zero bits, respectively, in an integer’s binary representation. Leading zero bits are the zero bits at the beginning, and trailing zero bits are the zero bits at the end of the binary representation of a number:

@Test
fun `counts leading and trailing zero bits in an integer`() {
    val a = 0b00111110.toUByte()
    assertEquals(2, a.countLeadingZeroBits())
    assertEquals(1, a.countTrailingZeroBits())
}

4.4. takeHighestOneBit() and takeLowestOneBit() Methods

The takeHighestOneBit() and takeLowestOneBit() methods return an integer with only the highest or lowest one bit, respectively, of the original integer:

@Test
fun `takes highest and lowest one bit in an integer`() {
    val a = 0b00111110.toUByte()
    assertEquals(0b00100000, a.takeHighestOneBit())
    assertEquals(0b00000010, a.takeLowestOneBit())
}

4.5. rotateLeft() and rotateRight() Methods

The rotateLeft() and rotateRight() methods rotate the bits of an integer to the left or right, respectively, by a specified number of places. The spaces left by the rotation are filled with the bits that were rotated out of the integer:

@Test
fun `rotates bits left and right in an integer`() {
    val a = 0b00111110.toUByte()
    assertEquals(0b11110001.toUByte(), a.rotateLeft(3))
    assertEquals(0b11000111.toUByte(), a.rotateRight(3))
}

5. Conclusion

In this article, we’ve reviewed the bit manipulation methods provided by Kotlin, including bitwise operations, shifts, and bitmasks.

By using these methods, we can manipulate individual bits within an integer value, including setting and clearing specific bits, and extracting specific bits from an integer. These operations can be useful in a variety of contexts requiring low-level manipulations while working with binary data.

The source code of all these examples can be found over on GitHub.