原码,反码,补码与模

2019-04-13 14:11发布

// 示例0 uchar a = 0; while (1) { a++; }   执行上面的代码,记录a的取值将会得到这样的结果: 0,1,2,……,255,0,1,2,……   我们发现255之后紧跟的是0。从二进制来看就是说[1111 1111]加1之后的结果是[0000 0000]。学习过数字电路加法器的同学并不会觉得有什么不妥。代码中的这一现象就是数字电路的特点决定的。   我们生活中也有类似这样的例子。时针的取值在1到12之间循环,也可以认为是在0到11之间循环,更符合编程和数学习惯。   上面两种情况都可以用一个数学工具来刻画——。简单来说,给定一个正整数N作为模,对于任意一个整数n对N取模,就是加上或者减去若干个N,使其落在0到N的范围内。举两个例子,我们给定N=13,56对13取模就是56 - 4 * 13 = 3,-17对13取模就是(-17) + 2 * 13 = 9。不管n取什么值,对13取模的结果只能是0到12之间。
  接下来研究整数在内存中是如何表示的,先来看几个基本数据类型: 数据类型 字节数 范围 uchar 1 0~255 char 1 -128~127 short 2 -32768~32767 每一种数据类型都是由固定长度的二进制组成,表示一定范围的整数。uchar和char都是一个字节长度,表示256个整数,不同的是uchar表示的都是正数,char表示的有正有负。 // 示例1 uchar a = 3; uchar b = 254; uchar c = a + b; // 示例2 char a = 3; char b = -2; char c = a + b;   上面分别是两个类型的加法运算,如果输出结果你会发现两个示例c的结果都是1。结合下面一张表来分析 uchar 二进制编码 char 3 0000 0011 3 2 0000 0010 2 1 0000 0001 1 0 0000 0000 0 255 1111 1111 -1 254 1111 1110 -2 253 1111 1101 -3 252 1111 1100 -4 观察可以发现,两个示例中的变量a,b,c对应的二进制编码完全一致。也就是说两个示例在计算机内部本质上并无二致。   仔细想想,其实也好理解。由于计算机遵循着模的运算,-2对256取模的结果是254,而3加254再对256取模的结果是1。uchar和char只是人为做出的规定,一个全部表示正数,一个既有正数也有负数,就像物理中选择了不同的参考系,看问题的角度不同而已。所以uchar和char的类型强制转换也只是切换参考系而已。
  现在我们正儿八经开始讲原码,反码和补码。我们以-2为例 原码 反码 补码 1000 0010 1111 1101 1111 1110 可以看的出来-2的补码和我们上面表中的编码是一致的。书上也说了,计算机中就是补码的形式。 Q: 既然这样,干吗还弄个原码和补码出来?
A: 看看原码和补码,哪个更容易联想到-2?   显然忽略最高位,原码就是2,最高位1表示负数的话,整个原码就是-2了。而补码则有点费劲。原码,反码到补码其实提供了一种计算机器码的方法。
1. 最高位始终用1表示负数,低七位表示绝对值,就形成了原码。
2. 原码 ===高位保持不变,其他位取反===> 反码 。
3. 反码===加1===>补码。
所以,反码完全沦为一个过渡。这里-128无法使用这一方法得到补码,因为我们无法只用7位为128编码。 Q: 为什么这么算就能得到机器码?巧合?
A: 我们先放一放这个问题,等下再讲。先讲另外一个计算机器码的方法。   我们再来看254和-2,前面已经讲过-2对256取模的结果就是254,在uchar下254的编码就是char下-2的编码。所以-128到-1的负数加上256得到正数(就是对256取模)在uchar下的编码就是所要求得char类型的机器码。是不是这种方法更本质更自然更简洁一点呢?而且还不遗漏-128。   回过头来看上面的问题,原码到反码再到补码,其实也可以从数学上得到解释。1000 0010低七位取反加1相当于(1111 1111 - 0000 0010) + 0000 0001= 1111 1110即255-2+1=-2+256=254。(注意:取反可以演变为加法,0101 1010取反=1111 1111 - 0101 1010 = 1010 0101)   以上观点是阅读他人成果加上自己思考的产物,难免有所偏颇,欢迎各位批评指正。