Bit operations in kotlin

Kotlin Bit Operations

announcement - icon

As a seasoned developer, you’re likely already familiar with Spring. But Kotlin can take your developer experience with Spring to the next level!

  • Add new functionality to existing classes with Kotlin extension functions.
  • Use Kotlin bean definition DSL.
  • Better configure your application using lateinit.
  • Use sequences and default argument values to write more expressive code.

By the end of this talk, you’ll have a deeper understanding of the advanced Kotlin techniques that are available to you as a Spring developer, and be able to use them effectively in your projects.

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`()

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`()

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`()

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.

Читайте также:  font-weight

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`()

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`()
@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`()

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`()

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`()

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`()

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`()

5. Conclusion

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

Читайте также:  Пример работы CSS

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.

Источник

Using Bitwise Operators in Kotlin

announcement - icon

As a seasoned developer, you’re likely already familiar with Spring. But Kotlin can take your developer experience with Spring to the next level!

  • Add new functionality to existing classes with Kotlin extension functions.
  • Use Kotlin bean definition DSL.
  • Better configure your application using lateinit.
  • Use sequences and default argument values to write more expressive code.

By the end of this talk, you’ll have a deeper understanding of the advanced Kotlin techniques that are available to you as a Spring developer, and be able to use them effectively in your projects.

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 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.

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 2 bits , while the left shift is multiplication by 2 bits . 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.

Источник

Оцените статью