关于这点其实很多人一知半解,今天特此做一个梳理。
看一个链接热热身吧。
https://blog.csdn.net/qq_40099776/article/details/86740454
正式进入话题。
首先看几个例子:
假设计算机字长为8位
1的原码(8位):0000_0001
反码、补码跟原码一样。
-1的原码(8位):1000_0001
反码 :1111_1110
补码 :1111_1111
0有+0和-0之分
+0的原码(8位):0000_0000
反码、补码同。
-0的原码(8位):1000_0000
反码 :1111_1111
补码 : 0000_0000
总之,正数的原码、反码、补码一样;负数的原码就是{符号位1,绝对值原码},反码等于符号位除外的各位取反,补码等于反码加1。
其实作为硬件工程师,不需要理解太底层太原理的东西,可以直接记住上面的原则就可以了。
二进制数是在通常所见到的十进制数之后发展起来的一种新的数的表示形式,然后我们该如何表示一个二进制数呢,在通常意义上我们像十进制那样,给一个二进制0,1串,加上正负号,或者有符号,无符号的某种标志,就可以表达清楚了。然而作为二进制或者其他进制,它存在的意义不再是为了人能够清楚的计数,而是为了满足机器(计算机)。所以,诞生各种二进制数表示法,原码,反码,补码,而且,最关键一点,为了计算效率和方便,计算机中数值还都是按照补码存放的,这一点相信大家应该都清楚。
这里截取一段话:
8位二进制能表示的数范围是2的8次方,即256,所以带符号整数的范围就是-128到127,无符号整数的范围是0-255。
二进制是计算技术中广泛采用的一种 数制。二进制数据是用0和1两个数码来表示的数。它的
基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。
采用二进制原因:
容易表示
二进制数只有“0”和“1”两个基本符号,易于用两种对立的物理状态表示。例如,可用"1"表示电灯开关的“闭合”状态,用“0”表示“断开”状态;晶体管的导通表示“1”, 截止表示“0”;电容器的充电和放电、电脉冲的有和无、脉冲极性的正与负、电位的高与低等一切有两种对立稳定状态的器件都可以表示二进制的“0”和“1”。而 十进制数有10个基本符号(0、1、2、3、4、5、6、7、8、9),要用10种状态才能表示,要用电子器件实现起来是很困难的。
运算简单
二进制数的 算术运算特别简单, 加法和 乘法仅各有3条运算规则( 0+0=0,0+1=1,1+1=10和0×0=0,0×1=0,1×1=1 ),运算时不易出错。[其实计算机处理 算术运算时都是加法和移位,并没有乘除法,如11B左移一位就成了110B,11B是十进制的3,而110B是6,看看是不是等于乘二,左移乘,右移就除,哈哈,好玩吧]此外,二进制数的“1”和“0”正好可与 逻辑值“真”和“假”相对应,这样就为计算机进行逻辑运算提供了方便。 算术运算和逻辑运算是计算机的 基本运算,采用二进制可以简单方便地进行这两类运算。
这里插播一下有符号数与无符号数:
一、无符号整数
无符号数(Unsigned number)是相对于有符号数而言的,指的是整个机器字长的全部二进制位均表示数值位,相当于数的绝对值。
注意:字符类型的-1用二进制表示是“1111 1111”(16进制为FF);而不是我们更能理解的“1000 0001”。
二、带符号整数
有符号整数可表示正整数、0和负整数值。其二进制编码方式包含 符号位 和 真值域。 我们以8bit的存储空间为例,最左1bit为符号位,而其余7bit为真值域,因此可表示的数值范围是{-128,…,127},对应的二进制补码编码是{10000000,…,01111111}。
1、8位二进制所能表示的无符号整数范围为0~255:
①8位二进制所能表示的无符号整数最小为:
二进制数11111111
=十进制数0
②8位二进制所能表示的无符号整数最大为:
二进制数11111111
=十进制数2的8次方-1
=255
2、8位二进制所能表示的带符号整数范围为-128~127:
①8位二进制所能表示的带符号整数最小为:10000000;
②8位二进制所能表示的带符号整数最大为:01111111。
拓展:
转换:
(1)无符号整数转换为有符号整数 :
看无符号数的最高位是否为1,如果不为1(为0),则有符号数就直接等于无符号数;如果无符号数的最高位为1,则将无符号数取补码,得到的数就是有符号数。
(2)有符号整数转换为无符号整数:
看有符号数的最高位是否为1,如果不为1(为0),则无符号数就直接等于有符号数;如果有符号数的最高位为1,则将有符号数取补码,得到的数就是无符号数。
总结:有符号数与无符号数之间的转换,都要看要转换的数的最高位是否为1,如果不为1,则转换结果就是要转换的数的本身;如果为1,则转换结果就是转换的数(看作是负数)的补码。
下面在介绍一个经常被忽视的问题。关于正数负数的原,反,补码,大家应该已经了然于心了,但是对于0这个数,我们应该如何去定义它的表示,它的原,反,补码应该是什么呢?
先直接上结论:
在字长为8位的计算机上,
原码 | 反码 | 补码 | |
+0 | 0000 0000 | 0000 0000 | 0000 0000 |
-0 | 1000 0000 | 1111 1111 | 0000 0000 |
也就是说,如果是在字长为8位的计算机上,+0和-0的补码均为00000000。
这里,为了避免混淆,再次重申:
我们都知道,不管是负数和正数,在计算机内存中都是以补码来表示的,下面先介绍原码、反码和补码的概念和联系。
1、所谓原码就是前面所介绍的二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。
2、反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
3、补码表示法规定:正数的补码与其原码相同;负数的补码是在其反码的末位加1。
根据原码的定义:正零和负零的原码为:
+0 : 0000 0000 0000 0000 0000 0000 0000 0000 (32 bit)
-0 : 1000 0000 0000 0000 0000 0000 0000 0000
有点像左极限右极限。。但是计算按照上面定的原码表示的话,确实会出现这两种二进制串。。而为了解决这种-0与+0的问题,补码确实也比较奏效。
而反码为:
+0 : 0000 0000 0000 0000 0000 0000 0000 0000
-0 : 1111 1111 1111 1111 1111 1111 1111 1111
补码为:
+0 : 0000 0000 0000 0000 0000 0000 0000 0000
-0 : 1 0000 0000 0000 0000 0000 0000 0000 0000
因为你要考虑二进制表示的位数。你用8位表示一个整数。
那么-0的补码应该是10000000,而你只有8位,所以最高位的1其实不存在,所以是00000000。
可以看出,-0的补码发生溢出,舍弃最高位后,其跟+0在内存的表示一样,都是:
0000 0000 0000 0000 0000 0000 0000 0000
因此,
-1 的补码全是 1;1111 1111;
计算机的二进制系统如何表示0,以有符号位(signed)的 8 位二进制表示:
0000,0000⇒+01000,0000⇒−0
0000,0000⇒+01000,0000⇒−0
计算机不会凭白浪费掉这样的一个扩大自己表示能力的机会的,将 −0=1000,0000B−0=1000,0000B 指定为 −128−128,所以一个有符号位的8位二进制的值域为:−128−127−128−127,在一般意义上的
1111,11110111,1111
1111,11110111,1111
// C
short a = 128;
// 00000000, 10000000
char b = a;
printf("%d\n", b);
// -128
有点类似于unicode字符编码,计算机总是不会浪费自己的位宽。。所以0还是只有一个0,只是原码系统有正0和负0之分,补码系统只有一个0。
总结一句就是,正数和0的补码是它本身。
############################## 插播一段 ##################################
补码数(有符号数),没有-0 这个编码,
8位为例,10000000 这个编码不是 -0,是-128
现在很多编程语言例如C,C++认为这个编码不是一个正确的有符号数
8Bits 有符号数范围是 -127~127
这是 -0 的原码,不是补码
负数的原码是正数(该负数的绝对值)并且在高位补上1 表示符号。
负数的补码是它的相反数,也就是它的绝对值,负数用补码表示。
正数的原码是它自己,补码是他的相反数,正数用原码表示,这才是有符号数(补码数)的表示方法。
0 的相反数是它自己,绝对值也是它自己,
它即是原码,也是补码
所以 0的补码是0
当然也可用加法溢出得出 0的补码是0
############################## 插播结束 ################################
最后给个福利,来段代码:
########################### 关于求负数补码 #############################
负数在计算机中是用补码的形式存储的,正数在计算机中是用原码的形式存储的。
正数求原码直接将十进制转二进制即可,负数的补码是在原码的基础上除符号位外其余位取反后+1。
但是用这种方式求负数补码用编程实现不太方便,下面介绍一种用编程实现起来较简便的求负数补码的方法:
-
求出负数绝对值的原码
-
从原码的最后一位数码位往前数,当遇到第一个1时停在此位置
-
将第一个1前面的数码全部求反
比如:(字长为8位)
求-127的补码:
127的原码: 01111111
-127的补码: 10000001
求-80的补码:
80的原码: 01010000
-80的补码: 10110000
求-1的补码:
1的原码: 00000001
-1的补码: 11111111
这种方式用编程实现起来比较方便,在原码的基础上除符号位外其余位取反后+1的方式要考虑进位。
#include<stdio.h>
#include<memory.h>
#include<string.h>
#include<stdlib.h>
int arr[8]; //范围为 -128 ~ +127
void Complement(int number) //求负数补码
{
number=abs(number); //取绝对值
int i=7;
while(number!=0) //求出其绝对值原码
{
arr[i]=number%2;
i--;
number/=2;
}
for(i=7;i>=0;i--)
{
if(arr[i]==1)
{
int j;
for(j=0;j<i;j++) //取反
if(arr[j]==1)
arr[j]=0;
else
arr[j]=1;
break;
}
}
for(i=0;i<=7;i++)
printf("%d",arr[i]);
printf("\n");
memset(arr,0,sizeof(arr)); //重置数组
}
int main()
{
int i;
for(i=-128;i<=-1;i++) // 求 -128 ~ -1 的补码
Complement(i);
return 0;
}
负数补码还原成原码是这样的:
-
从补码的最后一位数码位往前数,遇到第一个1时停止
-
将第一个1前的全部数码取反
-
将第一个数码置1(原来肯定是0,因为我们这里只讨论负数的补码)
为巩固知识点写下此篇博客,理解不是很深刻,如有错误,欢迎大家指正。