Bitwise Operators

Go's bit-level operators: &, |, ^, &^, << and >>, what each one does and what they are for.

Syntaxa & b a | b a ^ b a &^ b a << n a >> n // whole numbers only

A whole number is stored as bits: a row of 1s and 0s. The number 5 is 101. Bitwise operators work on those bits, one position at a time. They exist so that one number can carry many yes/no answers, and because moving bits sideways is the cheapest way to multiply or divide by two. They accept whole numbers only, never fractional ones.

The six operators

  • a & b keeps a 1 only where both sides have a 1. Use it to test or mask bits.
  • a | b gives a 1 where either side has a 1. Use it to switch bits on.
  • a ^ b gives a 1 where exactly one side has a 1.
  • a &^ b keeps the bits of a except where b has a 1. It switches bits off.
  • a << n moves the bits n places left, which multiplies by 2 n times.
  • a >> n moves them n places right, which divides by 2 n times, dropping what falls off the end.

Shorthand

^a with nothing on its left flips every bit of a. That gives the pair the house rule cares about: a &^ b is the same as a & ^b, an AND against a flipped b. Go spells it as one operator, and most languages have no such operator at all. There are assignment forms too: flags |= x means flags = flags | x, and &=, ^=, &^=, <<=, >>= follow the same rule.

Signs and precedence

Shifting a signed number right keeps its sign, so -8 >> 1 is -4 and -1 >> 20 is still -1. On an unsigned type, zeros come in from the left. Since Go 1.13 the shift count may be held in a signed variable, as long as it is not negative. & and the shifts bind tighter than + and ==, so flags&mask != 0 means (flags&mask) != 0.

Example

Example · go
package main

import "fmt"

// Each constant is a single bit inside one number: 1, 2 and 4.
const (
	canRead  = 1 << 0
	canWrite = 1 << 1
	canAdmin = 1 << 2
)

func main() {
	// | switches bits on, so perms now carries read and write.
	perms := canRead | canWrite

	// %03b prints the bits, padded out to three places.
	fmt.Printf("perms: %03b\n", perms)

	// & keeps only the bits both sides have, which tests one bit.
	fmt.Println("can write:", perms&canWrite != 0)
	fmt.Println("can admin:", perms&canAdmin != 0)

	// &^ switches bits off. These two lines are the same thing.
	fmt.Printf("no write: %03b\n", perms&^canWrite)
	fmt.Printf("no write: %03b\n", perms & ^canWrite)

	// ^ between two numbers gives 1 where exactly one side has 1.
	fmt.Printf("xor: %03b\n", canRead^canAdmin)

	// Left by 2 multiplies by 4; right by 2 divides by 4.
	fmt.Println(3<<2, 12>>2)
}

// Output:
// perms: 011
// can write: true
// can admin: false
// no write: 001
// no write: 001
// xor: 101
// 12 3

When to use it

  • Keep a set of on/off options, such as file permissions, inside one int.
  • Read one field out of a value that packs several numbers together.
  • Remove a single option from a flag set with &^ without disturbing the others.
  • Round a size up to the next multiple of a power of two using a mask instead of division.

More examples

A set of options in one number

This is the pattern behind file permissions and most option sets in the standard library. &^= is the one to remember: it removes a single option and leaves the others untouched, where writing style = style - flagBold would corrupt the whole set if the bit was already off.

Example · go
package main

import "fmt"

// 1 << iota gives each name the next bit along: 1, 2, 4, 8.
const (
	flagBold = 1 << iota
	flagItalic
	flagUnderline
	flagStrike
)

func main() {
	style := flagBold | flagUnderline

	// Switch one option on, switch another off.
	style |= flagItalic
	style &^= flagBold

	fmt.Printf("%04b\n", style)
	fmt.Println("italic on:", style&flagItalic != 0)
	fmt.Println("bold on:", style&flagBold != 0)
}

// Output:
// 0110
// italic on: true
// bold on: false

Signed and unsigned shifts differ

Shifting instead of dividing is a fair optimisation for values you know are not negative, and a quiet off-by-one for values that can be. Check the sign before you reach for >>, and prefer unsigned types when you are treating a number as a row of bits rather than as a quantity.

Example · go
package main

import "fmt"

func main() {
	signed := -8
	unsigned := uint8(248)

	// On a signed number the sign is kept: the top bit is copied in.
	fmt.Println("signed:", signed>>1)

	// On an unsigned number zeros come in from the left instead.
	fmt.Printf("unsigned: %08b\n", unsigned>>1)

	// Shifting a negative number right rounds downwards, while
	// dividing rounds towards zero. They are not the same.
	fmt.Println(-7>>1, -7/2)
}

// Output:
// signed: -4
// unsigned: 01111100
// -4 -3

Two values packed into one int

Packing is how a single number can be read and written as one indivisible unit, which matters once several goroutines share it. 0xff is 8 bits of 1, so & 0xff keeps the low byte and discards everything above it; the shift count and the mask width must always agree.

Example · go
package main

import "fmt"

func main() {
	// One number carrying two things: a state in the lowest 8
	// bits, and a counter in the bits above them.
	packed := 5<<8 | 200

	// Shift down to read the counter, mask to read the state.
	counter := packed >> 8
	state := packed & 0xff

	fmt.Println("counter:", counter)
	fmt.Println("state:", state)
}

// Output:
// counter: 5
// state: 200

Discussion

  • Be the first to comment on this lesson.