Bitwise Operators
Go's bit-level operators: &, |, ^, &^, << and >>, what each one does and what they are for.
a & b a | b a ^ b a &^ b a << n a >> n // whole numbers onlyA 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 & bkeeps a 1 only where both sides have a 1. Use it to test or mask bits.a | bgives a 1 where either side has a 1. Use it to switch bits on.a ^ bgives a 1 where exactly one side has a 1.a &^ bkeeps the bits ofaexcept wherebhas a 1. It switches bits off.a << nmoves the bits n places left, which multiplies by 2 n times.a >> nmoves 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
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 3When 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.
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: falseSigned 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.
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 -3Two 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.
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