原码、反码、补码的正(nao)确(can)打开方式
我们知道日常生活中使用的数分为整数和实数,整数的小数点固定在数的最右边,可以省略不写,而实数的小数点则不固定。在计算机中只能识别和表示“0”和“1”,而无法识别小数点,因此要想使得计算机能够处理日常使用的数据,小数点的问题是不可避免的。
关于计算机系统中实数的表示,在下篇文章中会讲解。本篇博客我们讲解的是整数在计算机系统中如何表示。
在各种大学教材,各种网站论坛中,对于整数编码表示方法的正确打开姿势(姿势要帅)如下:
1、机器数
机器数(computer number)是数字在计算机中的二进制表示形式。机器数有2个特点:
①、符号数字化。因为计算机硬件只认识两种物理状态(用0和1表示),因此数据的正负号在机器里就用一位二进制0或者1来区分。在计算机用一个数的最高位存放符号, 0代表符号“+”,以1代表符号“-”。
②、机器数的大小受机器字长的限制。机器内部设备一次能表示的二进制位数叫机器的字长,一台机器的字长是固定的。字长8位叫一个字节(Byte),机器字长一般都是字节的整数倍,如字长8位、16位、32位、64位。
比如在字长为8的计算机中,十进制数+5,其机器数为00000101;十进制数-5,其机器数为10000101。
2、真值
计算机机器数真正的值称为真值。因为机器数的最高位是符号位,所以我们在计算真值的时候要分区分开。
比如上面讲的机器数10000101,单纯作为一个二进制数,我们转换为十进制是133。但是其真值是不计算符号位的,其最高位的1表示"-"。所以10000101的真值为-5。
3、机器数的原码、反码、补码三种形式
前面我们讲过机器数是在计算机中的二进制表示形式,但是在计算机中,这种表现形式又分为原码、反码、补码等三种最常用的形式。
ps:下面举例都是字长为8。
①、原码
原码=符号位+真值
比如:
[+5]原码=0 0000101
[-5]原码=1 0000101
原码表示与真值对应直观,而且转换也简单。但是用原码进行加减运算的时候,会出现以下问题:
使用原码计算表达式:1 - 1 = 0
1 - 1 = 1 + (-1)= [00000001]原 + [10000001]原 = [10000010]原 = -2
注意:计算机是没有减法器,只有加法器,减法运算可以转换为加上那个数的负数。
我们发现通过原码计算1 - 1 表达式结果居然是 -2。所以早期计算机机器数采用原码编码的时候,在进行原码加减运算时,必须先判定是否是两个异号数相加或两个同号数相减,若是,则必须判定两个数的绝对值大小,根据判断结果决定运算结果符号,并用绝对值大的数减去绝对值小的数。也就是说用这样一种形式进行加运算时,负数的符号位不能与其数值部分一道参加运算,而必须利用单独的线路确定符号位。很显然,这样设计电路就很复杂,这是不经济实用的,为了解决这个问题,反码产生了。
②、反码
反码:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
我们用反码来计算 1 - 1
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
看上去结果好像是正确的了,但是大家发现没,结果是-0,虽然对于0的符号没有什么实际意义。但是在计算机中,0如果用原码和反码表示会有两种形式:
[+0]=[0000 0000]原=[0000 0000]反
[-0]=[1000 0000]原=[1111 1111]反
两种编码就两种编码吧,只不过是多占用一个计算机表示数的编码形式。只要结果是正确的,我们还是能够忍受的,然而。。。。
请用反码计算表达式 2 -1
2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]反 + [1111 1110]反 = [0000 0000]反 = [0000 0000]原 = +0
是不是很奇怪,原码计算 2 - 1 得到的结果居然是 0 。其实稍微分析计算过程我们也知道,再用反码进行加法运算的时候发生了进位,而由于字长为8,进位就直接省略了,便造成了错误。这肯定是不被允许的,所以采用反码的计算机解决办法如下:
反码的符号位相加后,如果有进位出现,则要把它送回到最低位去相加(循环进位)。
2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]反 + [1111 1110]反+[0000 0001]循环进位 = [0000 0001]反 = [0000 0001]原 = +1
13 - 6 = 13 + (-6)= [0000 1101]原 + [1000 0110]原 = [0000 1101]反 +[1111 1001]反+[0000 0001]循环进位=[0000 0111]反 = [0000 0111]原 =+7
采用反码运算虽然较好的解决了原码运算所遇到的困难或问题,但由于循环进位需要二次算术相加,延长了计算时间,这同样给电路带来麻烦。这时候补码登场了。
③、补码
补码:正数的补码与原码相同,负数的补码等于其反码的末位加1
我们来看下面这个例子:
2 - 1 = 1
2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]反 + [1111 1110]反 = [0000 0010]补 + [1111 1111]补 = [0000 0001]补 = [0000 0001]原 =+1
9 + 12 = 21
9 + 12 = [0000 1001]原 + [0000 1100]原 = [0000 1001]补 + [0000 1100]补 = [0001 0101]补 = [0001 0101]原 = 21
我们发现补码运算就很简单了,产生的进位直接舍去,而且不做多余的操作也解决了进位的问题。还有 +0 和 -0 的表示,在原码和反码都有两种形式,但是补码却只有一种:
[+0]=[0000 0000]原=[0000 0000]反=[0000 0000]补
[-0]=[1000 0000]原=[1111 1111]反=[0000 0000]补
就这样我们完美的解决了计算机中整数运算的问题。计算机的机器数采用补码的形式,我们在做算术运算的时候,既不需要额外的判断,又能得到准确的结果。
看上去本文应该结束了,然而......
请求出 127+1 的值
4、溢出
接着上面抛出的问题,127+1的值,我们现在程序中看看:
public static void main(String[] args) {
byte x = 127;
byte y = 1;
byte k = (byte) (x+y);
System.out.println(k); //-128
System.out.println(Byte.MIN_VALUE+"~"+Byte.MAX_VALUE); //-128~127
}
byte在计算机正好是一个字节,也就是8位二进制序列。我们发现127+1结果不是128,反而是-128,这就是结果发生了溢出。因为byte表示数的范围是-128-127,128超出了这个范围。用补码计算如下:
1 + 127 = [0000 0001]原 + [0111 1111]原 = [0000 0001]补 + [0111 1111]补 = [1000 0000]补
我们发现这个数的符号位没有发生进位,但是数值最高位发生了进位。在看前面的2-1
2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]补 + [1111 1111]补 = [0000 0001]补 = [0000 0001]原 =+1
这个表达式符号位和数值最高位发生了进位,但是结果却是正确的。总结如下:
只有一个高位进位或者符号位进位就为溢出的规则。
溢出是每种编码在运算时都不可避免的,一般来讲结果超过字长所表示数的范围都会发生溢出。而判断机器是正常进位还是溢出的基本依据,在微型机中可用异或电路来实现上述的判断。在实际编码中解决办法也很简单,就是将结果用更大范围的编码形式接收即可。比如两个byte类型的数相加,我们用 int 来接收即可。
public static void main(String[] args) {
byte x = 127;
byte y = 1;
int k = x+y;
System.out.println(k); //128
System.out.println(Byte.MIN_VALUE+"~"+Byte.MAX_VALUE); //-128~127
}
所以我们可以说用补码进行运算,在不考虑溢出的情况下,结果都是正确的。确实也是这样,但是......
请求出 -128 的补码?
5、剧情反转
上面的给出的问题,-128 的补码,我们首先想到去求它的原码,嗯,原码应该是 [1000 0000],不对,第一位不是符号位吗,[1000 0000]应该表示 -0。那应该怎么用原码表示 -128呢,我们发现字长为 8 的计算机用原码是无法表示的,反码也是一样。我们看看补码,用 -127- 1 的表达式结果来计算 -128 的补码:
(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
-128的补码形式为 [1000 0000],我们能通过算术表达式得到某个数的补码形式,但是为什么直接就求不出来?那么计算机自己是怎么实现的呢?
再来看这样一个问题:我们日常使用的钟表,比如现在钟表指向的是 10点钟,我要将钟表调整到 6 点钟,则有两种拨法:
①、顺时针将时针拨动 8 格
②、逆时针将时针拨动 4 (12-8) 格
由此给大家普及一个概念叫 “模”,钟表便是一个典型的模运算系统,其模数为12。
同理,对于十进制两位数,在将结果百位舍掉的情况下,50可以用60-10得到,或者60+90得到。这里的90也就是100-10得来的,那么我们就说十进制两位数运算系统的模数为100。
我们判定:两个相加等于模的数互为补数。
在模表示的范围内做减法运算,可以将“X-Y”的减法变更为“X+Y的补数“的加法,当然这里不考虑结果溢出。
上面我们举的例子都是大数减小数,如果是小数减大数会怎样?
如果是10-80,结果应该是-70。但是如果按照 10+(100-80)的说法,结果是30。很明显,30和-70不是同一个结果,而且也没有产生百位进位。那我们应该怎么办呢?
解决办法很简单,就是让这两个数相等,而且这正好解决了负数的表示方法,-70的绝对值的补数正好是30。
但是问题又来了,这里的30已经表示正数30了,现在又表示负数-70,那我们怎么知道它到底表示哪个数?
为了解决这个问题,我们给这套规则规定一个范围,原来是0~99的正数,现在既然要用部分正数来代替负数了,那就要规定一个范围来使得一个数只代表一个含义,正好一人一半,0~49这个区间就代表正数,50~99的区间就用来代表各自补数的负值,例:98就代表-2
所以0-99的编码数可以表示的数的范围为 -50-49。
我们解决了十进制两位数的减法运算,那么在字长为 8 的计算机系统中,我们又该如何呢?
8位二进制数可以表示的数为2的8次方,0-255,一共 256 个数,0也要占用一位数。所以我们说 256 是8 位二进制数的模,这和上面说的十进制两位数0-99,模为100是一样的。
我们按照前面讲的逻辑,一半的数0-127,代表其正数本身,另一半的数128-255表示其补数的负值,即-1~-128。
所以而 “X-Y”的减法 就用 “X+Y的补数” 的加法来表示,即将减法的形式转换为加法的形式了,而且计算结果还是正确的。
注意:这里还是一样,不考虑结果的溢出,也就是计算值和结算结果都必须在-128~127之间,一旦超过这个范围,结果就不准了,这也是程序员日常编码说的int=int+int,如果结果大于int类型表示的范围,那得出来的结果肯定不准。
由此我们得出来的结论是:
计算机编码其实并没有什么所谓的符号位,但是由于计算机没有减法运算,为了将负数变为某个可以运算的编码来进行加法运算,补码产生了。这也间接说明了正数的补码是不变的,而负数的解决办法是首位不变,其余的取反再加1。
我们上面说的补码怎么得来的,就是 模-绝对值 。
所以这个时候我说让你求 -128的补码,你马上就会知
256 - |-128|=128 而128的二进制补码是不是就是 [1 0 0 0 0 0 0 0]
让你求 -1 的补码,你马上就会知
256 - |-1| = 255,其255的二进制补码形式就是[1111 1111]
注意:关于这样求补码的具体数学证明,请参考《计算机组成与系统结构》。
6、总结
本篇文章你可以直接从第 5 点开始看,忘掉计算机编码的什么首位符号位,忘掉计算机补码是由原码取反加1,回归简单直白的理解。计算机是机器,硬件能理解的只有高低电平,也就是0或者1。它知道什么是符号位吗?这些规则只不过是为了更好的完成减法运算所yy出来的。
大学也学过这些编码方式,但是都是背书式记忆,希望这篇文章能给大家带来一些帮助。
个人见解,如有错误欢迎大家抛砖!!!
参考书籍:《计算机组成与系统结构》
参考文章: https://www.zhihu.com/question/20458542?sort=created
原码、反码、补码的正(nao)确(can)打开方式的更多相关文章
- Java基础-原码反码补码
Java基础-原码反码补码 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 注意,我们这里举列的原码和反码只是为了求负数的补码,在计算机中没有原码,反码的存在,只有补码. 一.原码 ...
- 大数据学习--day02(标识符、变量、数据类型、类型转换、进制转换、原码反码补码)
标识符.变量.数据类型.类型转换.进制转换.原码反码补码 标识符: java50个关键字不能做标识符,以数字开头不能做标识符(这个老是忘记写一个类名的时候) 变量: 变量分为成员变量和局部变量,注意作 ...
- JAVA:二进制(原码 反码 补码),位运算,移位运算,约瑟夫问题(5)
一.二进制,位运算,移位运算 1.二进制 对于原码, 反码, 补码而言, 需要注意以下几点: (1).Java中没有无符号数, 换言之, Java中的数都是有符号的; (2).二进制的最高位是符号位, ...
- 原码 & 反码 & 补码 & 详解
本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希 ...
- Java学习第五篇:二进制(原码 反码 补码),位运算,移位运算,约瑟夫问题
一.二进制,位运算,移位运算 1.二进制 对于原码, 反码, 补码而言, 需要注意以下几点: (1).Java中没有无符号数, 换言之, Java中的数都是有符号的; (2).二进制的最高位是符号位, ...
- C语言原码反码补码与位运算.
目录: 一.机器数和真值 二.原码,反码和补码的基础概念 三.为什么要使用原码,反码和补码 四.原码,补码,反码再深入 五.数据溢出测试 六.位运算 ...
- python之计算机硬件基本认知_数据单位_进制间转换_数的原码反码补码
一:计算机硬件基本认知 cpu: 中央处理器. 相当于人的大脑.运算中心,控制中心. 内存: 临时存储数据. 优点:读取速度快,缺点:容量小,造价高,断电即消失. 硬盘: 长期存储数据. ...
- C 标识符, 数据存储形式(原码,反码,补码)
一. 标识符 第一个字母必须是英文字母或下划线 二. 数据存储形式(补码存储) 最高位是符号位 ---- 0表示整数 ; 1 表示负数 1. 正数:原码 = 反码 = 补码 例子 : (10) 原码 ...
- java基础知识-原码,反码,补码
1.正数:原码,反码,补码:都一样. 2.负数:和正数的储存方式不同,负数都是以补码形式存储的. <1>负数的补码 把负数的原码除了符号位取反后再+1. <2>负数的原码 把对 ...
随机推荐
- TensorFlow问题:The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
1. 问题描述 The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available o ...
- visual Studio 无法调试,提示程序跟踪已退出
今天在打码出现了vs无法调试,我在网上查了很久没有发现一个方法. vs点击启动时,出现了一下提示 程序"[12648] *.vshost.exe: 程序跟踪"已退出,返回值为 0 ...
- win10 uwp 截图 获取屏幕显示界面保存图片
本文主要讲如何保存我们的屏幕显示的,保存为图片,也就是截图,截我们应用显示的. UWP有一个功能,可以截图,RenderTargetBitmap 我们首先写一个Grid,我们需要给他名字,我这里给他S ...
- 【装逼利器效率软件】一张图问你想不想用Launchy
简述:Launchy博客园很多文章,长篇大论文字太多. 一张图问你想不想用? 长话多说: 一.设置Launchy扫描目录,安装后会默认,个人推荐自定义目录比较好 二.自行建立快捷方式别名文件夹,存放各 ...
- 阿里巴巴Java开发规约插件p3c详细教程及使用感受
阿里巴巴Java开发手册 在进入正题介绍这款插件之前,首先来谈一下<阿里巴巴Java开发手册>,2017年年初,首次公开的阿里官方Java代码规范标准手册可以说是引起了全民(IT界)代码规 ...
- Linux入门(8)——Ubuntu16.04安装sublime text 3并配置Python开发环境
打开终端,添加sublime text 3的仓库: 按enter键继续 更新软件库: sudo apt-get update 安装Sublime Text 3: sudo apt-get instal ...
- C++const使用(06)
可以在类中使用const关键字定义数据成员和成员函数或修饰一个对象.一个const对象只能访问const成员函数,否则将产生编译错误. 常量成员 常量成员包括常量数据成员.静态常数据成员和常引用.静态 ...
- 视频加载logo 2
推荐这个网站 http://www.flaticon.com/ http://www.flaticon.com/search?word=spinner 旋转图标 http://www.flatico ...
- 版本控制之四:SVN客户端重新设置帐号和密码(转)
在第一次使用TortoiseSVN从服务器CheckOut的时候,会要求输入用户名和密码,这时输入框下面有个选项是保存认证信息,如果选了这个选项,那么以后就不用每次都输入一遍用户名密码了. 不过,如果 ...
- nodejs运行前端项目
有时候我们会创建一些小项目,只有几个简单html,没有引入一些前端框架,也没有使用webpack,那我们要如何让代码在我们本地跑起来呢? 当然是有很多种方法,IIS.wampserver等等好多都可以 ...