Skip to main content

位运算

计算机中的有符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用 0 表示“正”,用 1 表示“负”,而数值位,三种表示方法各不相同。 在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。

原码求补码

  1. 正整数的补码是其二进制表示,与原码相同。
  2. 求负整数的补码,将其原码除符号位外的所有位取反(0 变 1,1 变 0,符号位为 1 不变)后加 1。
  3. 0 的补码表示是唯一的。

补码求原码

对二进制数来说,先减 1 后取反和先取反后加 1 得到的结果是一样的

已知一个数的补码,求原码的操作其实就是对该补码再求补码

  1. 如果补码的符号位为“0”,表示是一个正数,其原码就是补码。
  2. 如果补码的符号位为“1”,表示是一个负数,那么求给定的这个补码的补码就是要求的原码。

位运算

  1. and 运算 &
  2. or 运算 |
  3. xor 运算 ^ xor 运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即(a xor b) xor b = a。

相同位不同则为 1,相同则为 0。

  1. not 运算 ~
  2. shl 运算 <<
  3. shr 运算 >>

Unlike other languages (c/c++, Java, Python, Javascript, etc), Go does not have a dedicated unary bitwise complement operator. Instead, the XOR operator ^ can also be used as a unary operator to apply one’s complement to a number. Given bit x, in Go ^x = 1 ^ x which reverses the bit. We can see this in action in the following snippet which uses ^a to take the complement of variable a.

判断奇偶

for i := 0; i < 10; i++ {
num := rand.Int()
if num&1 == 1 {
fmt.Printf("%d is odd\n", num)
} else {
fmt.Printf("%d is even\n", num)
}
}

整数指定位清零

AND 操作符是一个很好的将整数的指定位清零的方式。在下面的例子中,我们使用 & 运算符将数字后 4 位清零。

func main() {
var x uint8 = 0xAC // x = 10101100
x &= 0xF0 // x = 10100000
}

&^ 运算符

&^ 运算符叫做 AND NOT。它是一个 使用 AND 后,再使用 NOT 操作的简写。该操作符定义如下:

Given operands a,b
AND_NOT(a, b) = AND(a, NOT(b))
// 给定两个操作数 a,b
// 当 a=NOT(b)=1 时,操作 AND_NOT(a, b) 返回 1。
// 否则返回 0。

它有一个有意思的特性:如果第二个操作符返回 1。那么该位将会被清 0

AND_NOT(a, 1) = 0; clears a
AND_NOT(a, 0) = a;

下面这个代码片段使用 AND NOT 操作符来清掉 a 的后 4 位(1010 1011 到 1010 0000)

func main() {
var a byte = 0xAB
fmt.Printf("%08b\n", a)
a &^= 0x0F
fmt.Printf("%08b\n", a)
}
// prints:
10101011
10100000

移位运算符提供一种非常有趣的方式来设置一个二进制的值。我们使用 | 和 << 来设置 a 第三位的值

func main() {
var a int8 = 8
fmt.Printf("%08b\n", a)
a = a | (1<<2)
fmt.Printf("%08b\n", a)
}
// prints:
00001000
00001100

或者使用 & 和移位运算符来测试第 n 位是不是指定的值:

func main() {
var a int8 = 12
if a&(1<<2) != 0 {
fmt.Println("take action")
}
}
// prints:
take action

使用 &^ 和移位运算来给第 n 位置 0:

func main() {
var a int8 = 13
fmt.Printf("%04b\n", a)
a = a &^ (1 << 2)
fmt.Printf("%04b\n", a)
}
// prints:
1101
1001

移位运算符注意事项

当移动的是一个有符号的值,Go 将会自动的适配移位运算。在向右移位时,正负位上的值将会填充在缺失的位上。

BitMask

  • 用户权限
  • 老鼠试毒 有 1000 瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水 24 小时后就会死亡,问至少要多少只小白鼠才能在 24 小时内鉴别出哪瓶水有毒?

大小写转换

通过 ch ^ 32 的方式转换大小写,利用异或属于半加运算(不带进位的加法)的性质

ch := 'a'            //97
fmt.Println(ch ^ 32) //65
ch = 'F' //70
fmt.Println(ch ^ 32) //102

bitmap

参考资料

  1. 位运算-与或非
  2. Bit Hacking with Go
  3. 位掩码(BitMask)的介绍与使用
  4. bitmap 算法