Bitwise Operations in Go
In the below Go file we use bitwise operators to manipulate individual flags (on/off switches) in a single integer, where each bit position represents a different status.
First we’ll look at the code, and then we’ll explain how it works:
package main
import (
"fmt"
"strings"
)
// Define bit flags as constants, where each status is represented by a unique bit position
const (
StatusActive = 1 << iota // 1 << 0 which is 0001 (binary)
StatusAdmin // 1 << 1 which is 0010
StatusBanned // 1 << 2 which is 0100
StatusVerified // 1 << 3 which is 1000
)
// Stringify statuses for easier output
func stringifyStatus(status int) string {
statuses := []string{}
if status&StatusActive != 0 {
statuses = append(statuses, "Active")
}
if status&StatusAdmin != 0 {
statuses = append(statuses, "Admin")
}
if status&StatusBanned != 0 {
statuses = append(statuses, "Banned")
}
if status&StatusVerified != 0 {
statuses = append(statuses, "Verified")
}
return strings.Join(statuses, ", ")
}
func main() {
// Let's create a user status and use bitwise OR to combine different flags
var userStatus int
// Set the user as active and verified
userStatus |= StatusActive | StatusVerified
fmt.Println("Initial Status:", stringifyStatus(userStatus))
// Add "Admin" status
userStatus |= StatusAdmin
fmt.Println("After adding Admin:", stringifyStatus(userStatus))
// Remove "Verified" status using bitwise AND with NOT
userStatus &^= StatusVerified
fmt.Println("After removing Verified:", stringifyStatus(userStatus))
// Add "Banned" status
userStatus |= StatusBanned
fmt.Println("After adding Banned:", stringifyStatus(userStatus))
// Check if the user is an admin
if userStatus&StatusAdmin != 0 {
fmt.Println("User is an admin.")
} else {
fmt.Println("User is NOT an admin.")
}
// Remove "Admin" status using bitwise AND with NOT
userStatus &^= StatusAdmin
fmt.Println("After removing Admin:", stringifyStatus(userStatus))
// Check if the user is banned
if userStatus&StatusBanned != 0 {
fmt.Println("User is banned.")
} else {
fmt.Println("User is NOT banned.")
}
// Check if the user is an admin
if userStatus&StatusAdmin != 0 {
fmt.Println("User is an admin.")
} else {
fmt.Println("User is NOT an admin.")
}
}
Visualising Bits
In case you need a reminder of a what bit alignment and shifting look like:
Defining Bit Flags
Each status is assigned a unique power of 2 using bit shifting (1 << iota
).
This ensures each flag only affects a single bit:
StatusActive
has the binary value0001
(1 << 0
== 1 in decimal).StatusAdmin
has the binary value0010
(1 << 1
== 2 in decimal).StatusBanned
has the binary value0100
(1 << 2
== 4 in decimal).StatusVerified
has the binary value1000
(1 << 3
== 8 in decimal).
Setting Statuses
The following example combines two separate status flags:
userStatus |= StatusActive | StatusVerified
In binary, this results in 1001
(or 9 in decimal), which means both the “active” and “verified” flags are set.
Adding and Removing Flags
The following example sets the “admin” bit without affecting the other bits, resulting in 1011
(11 in decimal):
userStatus |= StatusAdmin
Comparing Statuses
Once userStatus
has combined flags we can use the &
operator to perform a bitwise AND
operation, which means it compares each bit of two integers. For each bit position, if both bits are 1, the result at that position will be 1; otherwise, it will be 0.
So what happens when we compare userStatus&StatusAdmin != 0
?
Well, StatusAdmin
is a bit flag defined as 1 << 1
, which results in 0010
in binary. This means that StatusAdmin
occupies the second bit position in the binary representation of an integer. When we do userStatus&StatusAdmin
, we’re effectively “masking” all bits except for the one represented by StatusAdmin (this is known as bit masking).
When we perform userStatus&StatusAdmin
, we get a result where only the bit corresponding to StatusAdmin
remains (and is set to 1
if that bit was already set in userStatus
). If this result is non-zero (!= 0
), it means the StatusAdmin
bit is set in userStatus
. If it’s zero, then StatusAdmin
is not set in userStatus
.
If we look at the code in bitwise.go
we’ll see userStatus
is initially set to include StatusActive
and StatusVerified
, so userStatus
is 1001
in binary (which is 9
in decimal). Remember StatusActive
occupied the first bit position (0001
), while StatusVerified
occupied the fourth bit position (1000
) so if setting both flags we get the combined 1001
.
Next, we add the StatusAdmin
flag with userStatus |= StatusAdmin
, making userStatus
now 1011
in binary (which is 11 in decimal). When we check if StatusAdmin
is set using userStatus&StatusAdmin != 0
we get back 2
from userStatus&StatusAdmin
(which is 0010
in binary) because we’ve bit masked the other bits that might have been turned on (if you recall, using &
turns each bit to zero except for those bits that were 1 in both numbers being compared), in order to reveal whether the StatusAdmin
bit was set on or not (i.e. 0
!= 2
so we know this person is an admin).