位运算

基础

程序中的数在计算机内存中都是以二进制的形式存在的,位运算就是直接对整数在内存中对应的二进制位进行操作。

二进制

十进制转二进制时,采用“除 2 取余,逆序排列”法

  1. 用 2 整除十进制数,得到商和余数;
  2. 再用 2 整除商,得到新的商和余数;
  3. 重复第 1 和第 2 步,直到商为 0;
  4. 将先得到的余数作为二进制数的高位,后得到的余数作为二进制数的低位,依次排序;
1
2
3
4
5
6
7
101 % 2 = 50 余 1
50 % 2 = 25 余 0
25 % 2 = 12 余 1
12 % 2 = 6 余 0
6 % 2 = 3 余 0
3 % 2 = 1 余 1
1 % 2 = 0 余 1

逆序排列即二进制中的从高位到低位排序,得到 7 位二进制数为 1100101,如果要转换为 8 位二进制数,就需要在最高位补 0。即十进制数的 8 位二进制数为 01100101

预备知识

根据冯~诺依曼提出的经典计算机体系结构框架。一台计算机由运算器,控制器,存储器,输入和输出设备组成。其中运算器,只有加法运算器,没有减法运算器(据说一开始是有的,后来由于减法器硬件开销太大,被废了 )

所以,计算机中的没法直接做减法的,它的减法是通过加法来实现的。你也许会说,现实世界中所有的减法也可以当成加法的,减去一个数,可以看作加上这个数的相反数。当然没错,但是前提是要先有负数的概念。这就为什么不得不引入一个该死的符号位。

原码,反码,补码的产生过程,就是为了解决,计算机做减法和引入符号位(正号和负号)的问题。

原码

最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值。

反码

原码最大的问题就在于一个数加上他的相反数不等于零

例如:0001+1001=1010 (1+(-1)=-2) 0010+1010=1100 (2+(-2)=-4)

于是反码的设计思想就是冲着解决这一点,既然一个负数是一个正数的相反数,那我们干脆用一个正数按位取反来表示负数试试

反码:正数的反码还是等于原码

负数的反码就是他的原码除符号位外,按位取反。

用反码解决原码问题

1
2
0001+1110=1111 (1+(-1)= - 0)
互为相反数相加等于0,解决。虽然是得到的结果是1111也就是-0

做一下两个负数相加

1
2
1110(-1)+1101(-2)=1011(-4)
1011 原码是 -3,反码是 -4

它解决了相反数相加的问题,但是让负数相加出错了

补码

所有数据的运算都是使用补码

补码:正数的补码等于他的原码
负数的补码等于反码+1。
(这只是一种算补码的方式,多数书对于补码就是这句话)

并不是反码+1就定义为补码。只不过是补码正好就等于反码加1罢了

原码反码补码的相互转换

  首先,正数的原码,反码,补码都是相同的。

  所以,这里讨论负数的原码,反码,补码的相互转化问题。

  • 负数原码和反码的相互转化

  负数原码转化为反码:符号位不变,数值位按位取反。

  如:

1
2
原码 1100 0010
反码 1011 1101

  负数反码转化为原码:符号位不变,数值位按位取反。

1
2
反码 1011 1101
原码 1100 0010
  • 负数原码和补码的相互转化

  负数原码转化为补码:符号位不变,数值位按位取反,末尾加一。

1
2
3
原码 1100 0010
反码 1011 1101 //符号位不变,数值位按位取反
补码 1011 1110 //末尾加1

  负数补码转化为原码:符号位不变,数值位按位取反,末尾加1。

1
2
3
补码 1011 1110
1100 0001 //符号位不变,数值位按位取反
原码 1100 0010 //末尾加1
  • 负数反码和补码的相互转化

  负数反码转化为补码:末尾加1。

1
2
反码 1011 1101
补码 1011 1110

  负数补码转化为反码:末尾减1(注意,此处的反码是指原码的反码)。

1
2
3
补码         1011 1110
原码的反码   1011 1101
//减法 借位

运算符

概览

WX20200103-183342@2x

按位与运算 &

定义:参加运算的两个数据,按二进制位进行“与”运算。

1
0&0=0  0&1=0  1&0=0  1&1=1

总结:两位同时为1,结果才为1,否则结果为0。

按位或运算 |

定义:参加运算的两个对象,按二进制位进行“或”运算。

1
0|0=0  0|1=1  1|0=1  1|1=1

总结:参加运算的两个对象只要有一个为1,其值为1。

异或运算符 ^

定义:参加运算的两个数据,按二进制位进行“异或”运算。

1
0^0=0  0^1=1  1^0=1  1^1=0

取反运算符 ~

定义:参加运算的一个数据,按二进制进行“取反”运算。

1
~1=0 ~0=1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~5
看原码
0 0000101

所有运算都是使用补码,所以得到它的补码(正数的原码、反码、补码都一样)
0 0000101

按位取反
1 1111010

补码转原码,按照上面的规则
符号位不变,数值位按位取反
1 0000101
末尾加1
1 0000110

得到
-6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~-6
先看原码
1 0000110

得到补码
符号位不变,数值位按位取反
1 1111001
末尾加1
1 1111010

按位取反
0 0000101

补码转原码
0 0000101

得到
5

左移运算符 <<

定义:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)

设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。

右移运算符 >>

定义:将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。

浮点数转二进制

举例:22.8125 转二进制的计算过程:

整数部分:除以2,商继续除以2,得到0为止,将余数逆序排列。
22 / 2 11 余0
11/2 5 余 1
5 /2 2 余 1
2 /2 1 余 0
1 /2 0 余 1
得到22的二进制是10110

小数部分:乘以2,取整,小数部分继续乘以2,取整,得到小数部分0为止,将整数顺序排列。
0.8125x2=1.625 取整1,小数部分是0.625
0.625x2=1.25 取整1,小数部分是0.25
0.25x2=0.5 取整0,小数部分是0.5
0.5x2=1.0 取整1,小数部分是0,

得到0.8125的二进制是0.1101

结果:十进制22.8125等于二进制00010110.1101

参考资料

7 分钟全面了解位运算

https://blog.csdn.net/zhiwen_a/article/details/81192087