Go > Core Go Basics > Basic Operators > Bitwise Operators (&, |, ^, <<, >>, &^)

Bitwise Operators in Go

This example demonstrates the usage of bitwise operators in Go, including AND, OR, XOR, left shift, right shift, and bit clear (AND NOT). Bitwise operators allow you to manipulate the individual bits within an integer.

Introduction to Bitwise Operators

Bitwise operators work on individual bits of integer values. They are fundamental for low-level programming, data manipulation, and certain types of algorithms. Go provides a comprehensive set of bitwise operators to perform these operations efficiently.Understanding bitwise operations is crucial for optimizing memory usage, manipulating hardware registers, and implementing algorithms where individual bits matter.

Bitwise AND (&)

The AND operator (&) returns 1 only if both corresponding bits are 1; otherwise, it returns 0. In the example, 5 (0101) & 3 (0011) results in 1 (0001). Each bit position is compared between a and b. If both bits are 1, the resulting bit is 1; otherwise, it's 0. This operation is frequently used for masking, where you want to isolate certain bits.

package main

import "fmt"

func main() {
	a := 5  // Binary: 0101
	b := 3  // Binary: 0011

	result := a & b // Binary: 0001, Decimal: 1
	fmt.Printf("a & b = %d\n", result)
}

Bitwise OR (|)

The OR operator (|) returns 1 if at least one of the corresponding bits is 1. If both bits are 0, then it returns 0. In the example, 5 (0101) | 3 (0011) results in 7 (0111). The OR operation is commonly used for setting specific bits in a number. It is also useful when the combination of bit flags need to be tested.

package main

import "fmt"

func main() {
	a := 5  // Binary: 0101
	b := 3  // Binary: 0011

	result := a | b // Binary: 0111, Decimal: 7
	fmt.Printf("a | b = %d\n", result)
}

Bitwise XOR (^)

The XOR operator (^) returns 1 if the corresponding bits are different. If the bits are the same (both 0 or both 1), it returns 0. In the example, 5 (0101) ^ 3 (0011) results in 6 (0110). XOR is valuable in cryptographic applications, such as simple encryption algorithms, because applying XOR twice with the same key restores the original value.

package main

import "fmt"

func main() {
	a := 5  // Binary: 0101
	b := 3  // Binary: 0011

	result := a ^ b // Binary: 0110, Decimal: 6
	fmt.Printf("a ^ b = %d\n", result)
}

Bitwise Left Shift (<<)

The left shift operator (<<) shifts the bits of the left operand to the left by the number of positions specified by the right operand. Zeroes are added to the right side of the bit sequence, effectively multiplying the value by 2 raised to the power of the shift value. In the example, 5 (0101) << 2 results in 20 (010100). Left shift can be very efficient for fast multiplication by powers of 2.

package main

import "fmt"

func main() {
	a := 5  // Binary: 0101
	shift := 2

	result := a << shift // Binary: 010100, Decimal: 20
	fmt.Printf("a << %d = %d\n", shift, result)
}

Bitwise Right Shift (>>)

The right shift operator (>>) shifts the bits of the left operand to the right by the number of positions specified by the right operand. In Go, the right shift is an arithmetic shift, meaning the sign bit (most significant bit) is preserved. In the example, 20 (00010100) >> 2 results in 5 (00000101). It is used for division by a power of 2. Note the sign bit is copied to the left for signed integers, and 0 is used for unsigned integers.

package main

import "fmt"

func main() {
	a := 20 // Binary: 00010100
	shift := 2

	result := a >> shift // Binary: 00000101, Decimal: 5
	fmt.Printf("a >> %d = %d\n", shift, result)
}

Bitwise AND NOT (&^)

The AND NOT operator (&^) clears the bits in the left operand if the corresponding bit in the right operand is set. The result has a 1 in a bit position only if 'a' has a 1 in that position AND 'b' has a 0 in that position. It's essentially equivalent to a & (~b), but more concise. In the example, 5 (0101) &^ 3 (0011) results in 4 (0100). AND NOT is often used for clearing specific bits in a value.

package main

import "fmt"

func main() {
	a := 5  // Binary: 0101
	b := 3  // Binary: 0011

	result := a &^ b // Binary: 0100, Decimal: 4
	fmt.Printf("a &^ b = %d\n", result)
}

Real-Life Use Case Section

Bitwise operators are extensively used in networking for tasks like manipulating IP addresses and subnet masks. They are also crucial in graphics programming for color manipulation and in embedded systems for controlling hardware registers. Furthermore, they are used in hash functions, data compression algorithms, and error detection/correction codes. Specifically, bitwise operators are commonly used to implement flag systems, where each bit represents a specific boolean condition.

Best Practices

Use bitwise operators judiciously. Overuse can lead to code that is difficult to understand. Add comments explaining the purpose of each bitwise operation. Always consider the data type and size of the operands to avoid unexpected results due to overflow or sign extension. For better readability, use constants with meaningful names instead of raw integer values when working with bit flags.

Interview Tip

Be prepared to explain the difference between bitwise operators and logical operators. For instance, distinguish between & (bitwise AND) and && (logical AND). Understand how bitwise operators can be used to optimize certain operations, such as multiplication/division by powers of 2. The interviewer could ask you to perform bit manipulation on the fly, for example, set the n-th bit of a number to 1.

When to use them

Use bitwise operators when you need to manipulate individual bits in an integer, optimize performance-critical sections of your code (e.g., working with hardware registers), or implement algorithms that inherently require bit manipulation (e.g., compression, encryption, networking protocols). If code readability is not significantly affected and your performance will benefit, then bitwise operators are a good choice.

Memory footprint

Bitwise operations are very efficient and usually have a small memory footprint because they work directly on the binary representation of data. Since the operators work directly on integer values there are no objects to create or destroy, reducing the overhead of the operation. They don't allocate or deallocate memory themselves.

Alternatives

For some tasks, boolean operators or built-in functions might offer clearer or more high-level alternatives to bitwise operators. For example, instead of using bitwise operators to manage a set of flags, using a dedicated boolean field for each flag can greatly increase readability at a small performance cost. In certain cases, it might be more beneficial to utilize lookup tables or data structures optimized for set operations. The most important thing is to consider the tradeoff between readability, performance and maintainability.

Pros

Bitwise operators are highly efficient for manipulating individual bits. They can provide performance improvements in situations where bit-level control is necessary. Can be easily used for simple encryption, data compression and flags system.

Cons

Bitwise operations can make code harder to read and understand if used excessively or without clear documentation. Misuse can lead to subtle bugs that are difficult to debug. They also only work directly on integer types and not on floating-point types or strings.

FAQ

  • What is the difference between & and && in Go?

    & is the bitwise AND operator, which operates on the individual bits of integers. && is the logical AND operator, which operates on boolean values and returns a boolean result. The bitwise operator compares each bit between two integers while the logical operators compare two booleans
  • Can bitwise operators be used with floating-point numbers in Go?

    No, bitwise operators in Go can only be used with integer types (int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64). Attempting to use bitwise operators with floats will cause a compile-time error.
  • How does the right shift operator (>>) work with negative numbers in Go?

    In Go, the right shift operator (>>) performs an arithmetic shift when used with signed integers. This means the sign bit (most significant bit) is preserved during the shift operation. If the number is negative (sign bit is 1), the leftmost bits will be filled with 1s to maintain the sign. If the number is positive, the leftmost bits will be filled with 0s.