计算机基础知识 - 二进制(位运算符、原码、补码)

前言

在被计算机的二进制弄得劝退之时,憋自己狠狠的补一下计算机二进制知识,就有了此文… …



一、深入理解二进制编码

1.什么是二进制编码

前面有文章提到过计算机中的单位关系,下面分析一下二进制的编码:

1字节=8位,1字节最大的二进制数是1111 1111。所谓二进制数就是最大只能是1,2就进位,与十进制数是一样的道理。无论什么几进制,左边是高位,右边是低位。

十进制数最大只能到9,单个数字是不能表示10的,我们所用到的10就已经是进位后的数字了。

二进制1111 1111转化位十进制:第1位表示几个20,第2位表示几个21,第3位表示几个22,…,第n位表示几个2n-1,由此可得到1111 1111为 1*27+1*26+1*25+1*24+1*23+1*22+1*21+1*20=255。

同理可得到计算n位二进制数的值:1*2(n-1)+1*2(n-2)+…+1*20



2.二进制的原码、反码和补码

2.1 原码

原码我们将十进制数字转换为二进制后的数字为原码。一个整数的绝对值大小转换为二进制数,正数不做变化,负数把二进制数的首部的0改为1表示负数。如byte类型的20的源码为0001 0100,共8位二进制数表示;如byte类型的-20的源码为1001 0100

反码和补码都是针对与带符号的负数而言的,带符号的正整数和无符号数的补码和反码与原码相同

2.2 反码

正数的反码与其原码相同,负数的反码就是除符号位(最高位)外其余各位取反。把二进制数中的1变为0,0变为1。如byte类型的-20的原码是1001 0100的反码就是1110 1011。反码的取值空间和原码相同且一一对应。

2.3 补码

正数的补码与其原码相同,负数的补码就是反码+1。一个整数,得到原码后再求其反码,再用反码加1就得到补码,常在计算负数时用到。如byte类型的-20的原码是0001 0100,反码1110 1011,补码1110 1100



3.理解有符号位和无符号位

负数在计算机中如何表示呢,书上的东西我就不说了,头大>_<!,我就站在一个码农的角度来说一说它:

用二进制的最高位表示符号,最高位是0表示正数,最高位是1表示负数。当然我们可以指定所需要的数值为有符号还是无符号。

1byte的无符号数值范围是0~255,有符号的数值范围是-128~127,后者用二进制数的第1位数表示符号

针对于有符号位的二进制数来说,上面的这种说法过于简单,容易造成混淆,比如byte类型-1的二进制就表示为1111 1111而不是我们所认为的1000 0001,不急,看下表。表中红色的 1 表示负数,它是符号位(最高位);**1**后面的数是补码(补码=反码+1),而这一部分代表的值就是将补码还原为原码的值(先减1再按位取反):

二进制值(1byte)十进制值
1000 0000-128
1000 0001-127
1000 0010-126
1111 1111-1

它由二进制值计算为负的十进制值:以为1000 0010为例,先取数值位的补码为000 0010,转化为反码-1,为000 0001,再去反码为111 1110,加上符号位为1111 1110,就是-126。(对于-128的计算方法记住就好)。

上面的这种1111 1111表示-1和1000 0000表示-128的方式(符号位+补码表示)如何更好的理解?那先看看是-1大还是-128大?

当然是-1大,以此对应就知道1111 1111 大于1000 0000了。且在计算机中,无论这个整数几字节,它全用1来表示-1(用二进制补码表示十进制数),若想获取到更小的数,就逐个减1,当数据只剩下符号位为1,其他全部为0时,就得到了此字节的最小值。

注意:1byte类型的二进制数1111 1111在指定无符号情况下表示255,在有符号情况下表示-1。一定要决定数据是否有符号!



4.机器数和计算机采用补码

4.1 机器数

计算机中的整数分为无符号的和带符号的。无符号的整数用来表示0和正整数;带符号的整数可以表示所有的整数。

在计算中内部,所有信息都是用二进制数串来表示,因此,正负号也必须用0,1来表示,通常我们用最高位的有效位来表示数值的符号,1表示负0表示正。比如当byte类型为8bit位时,第8位即为最高有效位,它用来表示数值符号,其余的7bit位用来表示数值大小。

用最高有效位的0/1来表示正/负,这种正负号数字化的机内表示形式就称为 机器数,而相应的机器外部用正负号表示的数称为 真值。将一个真值表示成二进制字串的机器数的过程称为 编码

无符号数字和带符号的正整数没有反码和补码一说(只有原码),带符号数的负数才存在不同的编码方式(原码、反码、补码)(这一点很多人混淆,混淆以后就对这个反码、补码一头雾水了,这里需要好注意一下)。也正因为如此,我们通常认为正整数的原码、补码和反码都是一样的。带符号整数的原码、反码、补码,具体怎么求,前面已经有提到。

只有带符号的整数采用补码存储表示,浮点数另有其他存储方式


4.2 采用补码表示数值

计算机中对于有符号数值的运算都是采用补码来完成的。它的优点和特征如下。

计算机中不存在绝对的无符号表示值,而是以有符号表示的正数部分表示了无符号数值,它们在计算的时候还是会采用补码来完成

采用补码的运算的特征

  1. 使用补码可以将符号位和其他位同一处理,同时减法也可以按照加法来算,即使用补码来表示的数,不管是加法或减法,都直接用加法运算即可实现。
  2. 两个用补码表示的数相加时,如果最高位(符号位)有进位时,则进位被舍弃。

采用补码运算的优点

  1. 使符号位可与有效值部分一起参加运算,简化运算规则,从而简化运算器的结构,提高运算速度。
  2. 加法运算比减法运算更容易实现,使用加法运算代替减法运算,进一步简化计算机中运算器的线路设计。

简单分析采用补码表示数值的原因

现分别采用原码、反码和补码进行加减法。假设字长位8bit位,如使用原码、反码和补码减法运算:

下面的补码需要先转换为反码,反码需要先转换为原码,才能计算得到十进制数值

十进制:		 1	   +    (-1)   =  0
二进制原码:	0000 0001 + 1000 0001 = 1000 0010 -> -2(×)
二进制反码:	0000 0001 + 1111 1110 = 1111 1111 -> -0(×)//这个数不对
二进制补码:	0000 0001 + 1111 1111 = 0000 0000 -> +0(√)

十进制:		 1	   +    (-2)   =  -1
二进制原码:	0000 0001 + 1000 0010 = 1000 0011 -> -3(×)
二进制反码:	0000 0001 + 1111 1101 = 1111 1110 -> -1(√)
二进制补码:	0000 0001 + 1111 1110 = 1111 1111 -> -1(√)

十进制:		 2	   +    (-1)   =  1
二进制原码:	0000 0010 + 1000 0001 = 1000 0011 -> -3(×)
二进制反码:	0000 0010 + 1111 1110 = 0000 0000 -> 原码:0111 1111(x)
二进制补码:	0000 0010 + 1111 1111 = 0000 0001 -> 1(√)
... ...

正如我们所见,使用补码计算全部正确(更多的栗子我就不举了),也仅此只有使用补码进行数值运算能正确。另外,采用补码表示数值还可防止0的机器编码有两个,+0-0

原码和补码表示0的时候会出现+0-0,比如在byte类型8bit为例:

byte类型+0-0
原码0000 00001000 0000
补码0000 00001111 1111

而使用补码表示的时候,只有0000 0000表示的是0,1000 0000表示的是-128了,并且,补码还使得数据范围比原码和补码表示的多出一位(解决了+0、-0的情况),如8bit情况下:

byte类型二进制范围十进制范围
原码1111 1111 ~ 0111 1111-127 ~ 127
反码1000 0000 ~ 0000 0000-127 ~ 127
补码1000 0000 ~ 0111 1111-128 ~ 127


5.解决补码加减运算

根据前面的分析,有十进制数转换为原码,再转换为反码,最后才到补码,之后进行计算后又再进行转换,一步又一步,甚是麻烦,这里就提出一种简便实现二进制补码计算的方法:

  1. 补码的加法原则:[x]补+ [y]补= [x+y]补
  2. 补码的减法原则:[x-y]补 =[x+(-y)]补= [x]补+ [-y]补

它是基于取余原理,推论我就不做了,感兴趣可以翻阅一下书籍。



二、二进制的位运算符

位运算符优先级很低的,低于单目运算、低于加减乘除法。对于运算符的详细分析猛戳我!

在前面对原码、反码和补码的分析中,我们知道计算机内部是以补码表示数值的,那么二进制运算符也是在补码的基础之上实现的。即所有十进制数使用二进制运算符时都是以补码计算的

两个数之间的运算

下列示例全部为byte类型,8bit位

1.&

左右都为真结果为真,如1010 1100 & 1001 0110 = 1000 0100

&&&的是有本质的区别&&表示逻辑与,就是判断左右逻辑是否都为真,当左边为false右边就不会判断;而&当左边为false时,还会再判断右边的。

int a = 1,b = 1;
if(a==0 & b++==2){}//这里b++被执行了
if(b==0 && a++==2){}//a++没有被执行
System.out.println("a="+a);//a=1
System.out.println("b="+b);//b=2

2.|

左右有一个为真则为真,如1010 1100 | 1001 0110 = 1011 1110

|||的区别与&&&的区别类似。||表示逻辑或,就是判断左右逻辑是否有一个为真,当左边为true右边就不会判断;而&当左边为true时,还会再判断右边的。


3.^异或

两个不相同结果为真,如1010 1100 ^ 1001 0110 = 0011 1010。。很有趣的一点是一个数字异或同一个数两次,得到它本身,如(5^10)^10 == 5。这一点性质经常用到。


4.<<左移

二进制位数左移,不分正负,低位补0。如1010 1101 << 1 = 0101 1010


5.>>右移

二进制位数右移,正数高位补0,负数高位补1


6.>>>无符号右移

又称为逻辑右移,不分正负,高位补0



一个数使用的运算

~取反码

它的针对于一个二进制数字而使用的。0转换为1,1转换为0。如~1010 == 0101



三、位运算符立大功

大家都知道在算法题中,熟练使用位运算符是非常有必要的,能提高算法性能,还能简单实现需求。

🐕位运算立大功!冲冲冲!!!

1.判断数的奇偶

使用&,判断二进制数最低位是0还是1,0就是偶数,1就是奇数

//因为位运算符优先级低于逻辑运算符,所以必须用()把a&1括起来
if((a & 1) == 0) 等价于 if(a % 2 == 0)


2.交换两个数

使用^,不使用临时变量实现两个数的交换

a ^= b;
b ^= a;
a ^= b;


3.求某int类型的数转二进制后,1的个数

x & (x-1)消去x最后一位知。循环使用x & (x-1)消去最后一位1,计算总共消去了多少次即可。

while(x != 0){
	count++;
	x & (x-1);
}

上面的解法可能有点看不懂,那么这里下面这种解法就很好理解了,就是不断的先右,先记录最低位是不是1,是就+1,不是就移动:

//或
while(x != 0){
    count += x & 1;
    x >>>= 1;//二进制右移一位
}


4.异或性质的应用与拓展

  • 数组中只有一个数出现一次,剩下都出现三次,找出出现一次的;
  • 数组中只有一个数出现一次,剩下都出现两次,找出出现一次的。
  • 数组中只有两个数出现一次,剩下都出现两次,找出出现一次的。


5.乘除法运算转换为位运算

根据数值的补码 左/右移 规则实现高效的 乘除法必须是不产生数据溢出的情况,就比如byte类型的8bit位数据范围是-128~127,就不是有byte类型的120 << 1的情况,这它会直接转换位 int类型 来计算。

乘法a * (2^n)等价于a << n

除法a / (2^n)等价于a >> n



6.取模运算转化成位运算

依赖于 & 位运算符,也必须是不产生数据溢出的情况

a % 2^n 等价于 a & (2^n-1),注意取余数必须是2的幂次。JDK1.8的hashmap计算hash值取余操作那一步采用的&运算符,h & (length-1) == h % length



7.实现加法

下面不使用加法运算符实现加法:

public int add(int A, int B) {
    while(B != 0){
        int ret = A ^ B;//对应位的和
        B = (A & B) << 1;//对应和的进位
        A = ret;
    }
    return A;
}


8.判断一个数是否是2的方幂

使用 n & (n-1) == 0 判断,true就是2的方幂。

if(n>0 && ((n&(n-1))==0)){
    //是
}else {
    //不是
}


9.求n的因子中所有2的乘积

使用 n & (-n) 求出n的因子中所有为2的乘积。

public int factor(int n){
    return n & (-n);//得到的数就是n的所有因子中2的乘积
}
/**示例:
         * 1.n=10   因子组成:2*5,1*10
         * n&(-n)=2
         *
         * 2.n=8    因子组成:2*2*2
         * n&(-n)=8
         */


四、写在最后的话

关于二进制的问题也是困扰我很久的,但是奈何时间琐碎,学习和记录的二进制知识点还是很少… …,之后有机会会再查阅一些资料持续学习。

当你在代码中写入上面这些位运算符,来替换简单的乘除法或是需要很多的代码才能实现的功能时,就显得很高大上,位运算立大功🐕。

  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值