在底层硬件中,运算其实是按照补码的形式来进行运算的,为什么要使用补码,使用原码进行运算有什么不好的地方?使用补码又会有什么好处?
CHIPSEA的24bitSigma Delta ADC输出的AD源码是带符号位吗?输出的AD码是源码还是补码?
本文带您解开这个答案。
Q1 : 使用源码进行底层运算有什么不好的地方?
第一,以8位单片机为例,sign int 表示的范围为 -127 ~+127,最高位为符号位,例如1000 0001表示-1,0000 0001表示+1。但这种表示方法缺点在于0可以有两种表示方式,0000 0000,表示+0, 1000 0000表示-0。其实本质上都是表示0。这样会浪费内存运算资源。
第二,在使用原码进行1-1和-1-127的运算时,如果按照原码来计算,计算机只能执行如下结果:
1-1 = (+1)+(-1) = [0000 0001]+[1000 0001]=[1000 0010]=-2 //计算错误
-1-127 = (-1)+(-127)= [1000 0001]+[1111 1111]=[1 1000 0000] = -0 //溢出舍弃,计算错误
Q2 : 那么使用补码进行底层运算有什么好处吗?
下面带大家对底层运算做一个相对系统的介绍。看完之后,相信大家会对补码这个巧妙的思想叹为观止。
1.机器数
一个数在计算机中实际的二进制表示方式,叫做这个数的机器数。显而易见,机器数是有“符号”这个概念的,因为正数和负数都必须能准确无误的表示出来。所以人们规定用一个数的最高位(最左面的一位)来表示符号,如果是正数,最高位是0,如果是负数,最高位是1。
由此我们不难理解,一个十进制数的的机器数并不是直接把十进制转换成二进制就完成了,还需要考虑符号的问题。比如说十进制数+3,用一个字节(8位)来表示,转换成机器数是0 0000011,如果是-3,那机器数应该是1 0000011。
2.真值
由于最高位是符号位,所以机器数的形式值不等于它表示的真正的值。例如上面的机器数1 0000011,它表示的是-3,而不是形式值131(把10000011直接转换成十进制数就是131),所以为了不混淆,我们把机器数表示的真正的数值叫做机器数的真值,显然机器数1 0000011的真值就是-3.
3.原码
一个数的原码是什么?就是符号位和这个数的绝对值,即用第一位表示符号位,其余位表示值,这是人比较容易理解的方式。以八位二进制为例:
因为第一位是符号位,所以机器的八位二进制能表示的范围就是:
我们知道,8位二进制一共有256种可能性,但是127乘以2是254,剩下的两种可能去哪里了?没错,它们用来表示正0和负0了,最高位是1,后面全是0则为负0,最高位是0,后面全是0则为正0,这一点也并不是很难理解。
4.反码是什么样的?正数的反码与原码一样,负数的反码是在原码的基础上,符号位不变,其他的各位取反,是1就变成0,是0就变成1。
从这里我们可以看到,如果给定一个反码,表示的是负数,那么我们无法直接看出它的真值,需要通过换算规则换算。
5.补码如何表示?正数的补码是本身,负数的补码是在原码的基础上,符号位不变,其余取反,最后整个数加1(包括符号位),也就是整个反码加1。
这里就比较有意思了,八位二进制全部为1,直接转换成十进制,应该是255,但是按照补码看待,却变成了-1,确实不那么容易理解。
6.看到这里,我们就会有一个疑问,原码是很好理解的,反码和补码都不那么直观,那么为啥要使用它们呢??答案就是因为计算机太愚蠢了!
人知道第一位是符号位,在计算的时候可以暂时忽略符号位,只对实际的数值进行计算,最后在结果中添上符号即可,但是要让计算机按我们的方式运算,电路设计就会变得非常复杂,于是人们就想如何能让符号位也参加运算,同时还能得到正确的结果,因为达到这种效果,会让电路设计变得简单。
7.根据运算法则,减去一个数等于加上这个数的相反数,那么用原码计算十进制表达式“1-1=0”就是这样的:
可以看到,如果使用原码,再让符号位参加运算,对于减法来说,计算的结果是错误的,所以在计算机内部不使用原码。
8.那么如果使用反码计算,情况有是怎么样的呢?
可以看到,真值部分的结果是正确的,但是多出了一个-0。原码和反码表示0的时候都占用了两个位置,一个正一个负,比如原码的,反码的是0和全1。
9.补码可以同时解决0的负号和同时两个值表示0的问题,注意,这才是计算机中真正在使用的办法,计算机中的数就是以补码的形式存放的。
原码 原码 补码 补码
1-1=(+1)+(-1)=[0000 0001]+[1000 0001]= [0000 0001]+[1111 1111]=[1 0000 0000]=0; //最高位溢出去掉,结果显示正确;
这里之所以得到全0,是因为一直加到最高位之后溢出了,剩下的八位就变成了全部是0,但是这样可以解决上面提到的两个问题,再看下面这种情况:
源码 源码 补码 补码
-1-127=(-1)+(-127)= [1000 0001]+[1111 1111] = [1111 1111]+[1000 0001]=[1 1000 0000]=[1000 0000]
// 1000 0000作为补码就是代表-128,因此计算正确。
得到的原因也是一直加上去最高位溢出了,剩下的8位就成了这样一个情况。 (-1)加上(-127)的正确结果应该是-128,在补码运算中,
代表的就是(-128),但是注意,实际上就是用原来使用原码的情况下,表示(-0)的那种情况来表示的(-128)。也就是最高位为1,其余位全是0。还要注意,这个最小值是没有原码和反码表示的。
如果按照规则求出它的原码,那么应该是
,显然这是不对的,它是溢出的产物,不能按普通方式退回去。
使用补码不仅修复了0的负号和两个编码的问题,而且由于溢出,原码表示-0的那种方式刚好能够表示一个最低数。这就是为什么八位二进制,使用原码和反码能表示的范围是[-127,127],而使用补码可以表示到[-128,127]。
Q3:还有一个问题。知道补码的情况下如何求原码?
<1>正数的补码就是原码,都一样。
<2>如果是负数,求补码的补码,就是原码。也就是符号位不变,其他各位取反,最后整个加1。
综上所述,得出了如下结论
<1>补码全是0,代表的是0。
<2>补码全是1,其实是-1。
<2>补码最高位是1,其他全是0,代表最小值。
<4>补码最高位是0,其余全是1,代表最大值。
<5>补码最高位和最低位都是1,其余是0,表示第二小的数。
<6>在一个固定字节的范围内,最小值减1会变成最大值,最大值减一会变成最小值,就像一个循环一样,以八位二进制为例,用图表示出来是这样的:
部分转载于: http://blog.sina.com.cn/s/blog_12ce411a90102w7pz.html 感谢博主分享!