原码、反码、补码

对于有符号的数而言:

  1. 二进制最高位是符号位:0表正数,1表负数
  • 1 -> 0000 0001-1 -> 1000 0001
  1. 0和正数的原码、反码、补码全一样
  • 1 -> 原码[0000 0001]
  • 1 -> 反码[0000 0001]
  • 1 -> 补码[0000 0001]
  1. 负数的反码 = 原码的符号为不变,其它位取反(1->0、0->1)
  • -1 ->原码[1000 0001]
  • -1 ->反码[1111 1110]
  1. 负数的补码 = 它的反码加1
  • -1 ->反码[1111 1110]
  • -1 ->补码[1111 1111]
  1. 计算机运算时,都取补码来运算,运算的结果也是补码
  • 1+11-1 = 1+(-1)3 & 2等运算将先取1、-1、3、2的补码,再进行对应的运算,示例见后文

位运算

均先转为二进制,然后对两个二进制数进行位运算。

  • 位与&:两位均为1时得1,否则得0
  • 位或|:任一位为1得1,否则得0
  • 位异或^:双目运算符,位不同时得1,相同时得0。例如3 ^ -2
  • 位取反^(某些语言中为~):单目运算符,0得1、1得0。运算结果中负数的绝对值比正数大1,即^a = b,则a + b = -1。例如^3 = -4^-3 = 2

注意,在Golang中,^即可以是位异或运算符,也可以是位取反运算符,单目时是位取反,双目时是位异或。

例如:3 & -23 | -23 ^ -2的值分别为?

1
2
3
4
5
6
7
3的补码 :0000 0011
-2的补码:1111 1110(原码:1000 0010,反码1111 1101)

运算 补码结果 反码结果 原码结果 十进制结果
3 & -2 = 0000 0010 -> 0000 0010 -> 0000 0010 -> 2
3 | -2 = 1111 1111 -> 1111 1110 -> 1000 0001 -> -1
3 ^ -2 = 1111 1101 -> 1111 1100 -> 1000 0011 -> -3

再例如,减法运算3 - 4 = ?,它等价于3 + (-4)

1
2
3
4
5
3的补码 :0000 0011
-4的补码:1111 1100(原码1000 0100,反码1111 1011)

运算 补码结果 反码结果 原码结果 十进制结果
3 - 4 = 1111 1111 -> 1111 1110 -> 1000 0001 -> -1

移位运算

  • 右移运算>>:符号位固定,其它位向右移动,低位溢出,符号位和中间缺失的位补0
  • 左移运算<<:符号位固定,低位补0

技巧:左移一位相当于乘2,右移一位相当于除2(但1右移1位后为0)。

位操作常见用途

  1. 位与&:常用来与对应位为1的值位与,取出值中的部分存在位,特别是存在于中间的某几位
  2. 位或|常用来合并设置。例如open()文件时,使用|符号合并多个打开文件的模式
  3. 异或^常用来找出只有单边设置了的属性,即找不同。例如找出两个数据中的不同字符(如Ruby和ruby哪里不同)
  4. 移位通常用于取边缘位、按2的倍数扩大或缩小

例如,进程退出状态信息为2字节16个位,**高8位是退出状态码,低8位中的低7位是导致进程退出的信号(如果是信号导致子进程退出的话),最高位是coredump的flag(即表示这个退出的进程是否进行了coredump)**。即:

假设该退出状态信息保存在变量exit_info中。

如果想取出退出状态码部分,由于它在高位最边缘,所以右移位最方便:

1
exit_code = exit_info >> 8

如果想取出信号部分,由于在中间的7个位上,用这些位的全1二进制做与运算最方面,与运算时,只有信号某位上为1时,与的结果才为1,这样就取出了信号为上的全部1信息位:

1
sig_info = exit_info & 127  # 127 = 0111 1111

同理,如果想取出coredump flag,则与128位与即可:

1
sig_info = exit_info & 128  # 128 = 1000 0000