二进制原码、反码、补码

2019-04-13 17:19发布

二进制原码、反码、补码 【原创文章,转载请保留或注明出处:】 1.基础概念和计算方法 在探求为何机器要使用补码之前让我们先了解原码反码和补码的概念.对于一个数计算机要使用一定的编码方式进行存储原码反码补码是机器存储一个具体数字的编码方式. 1.1模的概念 把一个计量单位称之为模或模数
补码的模为10000 0000
反码的模为1000 0000(从反码的定义也能够知道,即反码的运算不涉及符号位)
模的解释
在日常生活中,有许多化减为加的例子。例如,时钟是逢12进位,12点也可看作0点。
当将时针从10点调整到5点时有以下两种方法:
(1)时针逆时针方向拨5,相当于做减法: 1055
(2)时针顺时针方向拨7,相当于做加法:10(12-5)1255    (模为 12) 同理,计算机的运算部件与寄存器都有一定字长的限制(假设字长为8),因此它的运算也是一种模运算。当计数器计满8位也就是256个数后会产生溢出,又从头开始计数。产生溢出的量就是计数器的模,显然,8位二进制数,它的模数为28=256。在计算中,两个互补的数称为补码 1.2原码 原码就是符号位加上真值的绝对值即用第一位表示符号其余位表示值比如如果是8位二进制: [+1]原 = 0000 0001 [-1]原 = 1000 0001 第一位是符号位因为第一位是符号位所以8位二进制数的取值范围就是: [1111 1111 , 0111 1111] 即 [-127 , 127] 原码是人脑最容易理解和计算的表示方式 1.3反码 反码的表示方法是: 正数的反码是其本身 负数的反码是在其原码的基础上符号位不变,其余各个位取反. [+1] = [00000001]原 = [00000001]反 [-1] = [10000001]原 = [11111110]反 可见如果一个反码表示的是负数人脑无法直观的看出来它的数值通常要将其转换成原码再计算 1.4补码 补码的表示方法是: 正数的补码就是其本身 负数的补码是在其原码的基础上符号位不变其余各位取反最后+1. (即在反码的基础上+1) [+1] = [00000001]原 = [00000001]反 = [00000001]补 [-1] = [10000001]原 = [11111110]反 = [11111111]补 对于负数补码表示方式也是人脑无法直观看出其数值的通常也需要转换成原码在计算其数值 1.5其他概念
由于计算机中符号和数字一样,都必须用二进制数串来表示,因此,正负号也必须用01来表示。
用最高位0表示正、1表示负这种正负号数字化的机内表示形式就称为机器数”,
而相应的机器外部用正负号表示的数称为真值”,将一个真值表示成二进制字串的机器数的过程就称为编码。 2.为何要使用原码、反码和补码 现在我们知道了计算机可以有三种编码方式表示一个数对于正数因为三种编码方式的结果都相同: [+1] = [00000001]原 = [00000001]反 = [00000001]补 所以不需要过多解释但是对于负数: [-1] = [10000001]原 = [11111110]反 = [11111111]补 可见原码反码和补码是完全不同的既然原码才是被人脑直接识别并用于计算表示方式为何还会有反码和补码呢? 首先因为人脑可以知道第一位是符号位在计算的时候我们会根据符号位选择对真值区域的加减. (真值的概念在本文最开头). 但是对于计算机加减乘数已经是最基础的运算要设计的尽量简单计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂于是人们想出了将符号位也参与运算的方法我们知道根据运算法则减去一个正数等于加上一个负数: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法这样计算机运算的设计就更简单了. 于是人们开始探索 将符号位参与运算并且只保留加法的方法首先来看原码: 计算十进制的表达式: 1-1=0 1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2 如果用原码表示让符号位也参与计算显然对于减法来说结果是不正确的.这也就是为何计算机内部不使用原码表示一个数. 为了解决原码做减法的问题出现了反码: 计算十进制的表达式: 1-1=0 1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0 发现用反码计算减法结果的真值部分是正确的而唯一的问题其实就出现在"0"这个特殊的数值上虽然人们理解上+0-0是一样的但是0带符号是没有任何意义的而且会有[0000 0000]原和[1000 0000]原两个编码表示0. 于是补码的出现解决了0的符号以及两个编码的问题: 1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原 这样0[0000 0000]表示而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128: (-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补 -1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的) 使用补码不仅仅修复了0的符号以及存在两个编码的问题而且还能够多表示一个最低数这就是为什么8位二进制使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127]. 因为机器使用补码所以对于编程中常用到的32int类型可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值. 而且实际上并不是从1000000111111111依次表示-1-127,而是刚好相反的,从1000000111111111依次表示-127-1 用补码表示负数时:负数X2^n - |X|来表示,其中n为机器的字长n=8时,[-1]补 = 2^8 - 1 = 11111111, [-127]补 = 2^8 - 127 = 100000001 [-0]=2^8=00000000在补码表示法中只有一种表示,即00000000 3.二进制编码 1字节 = 8位,所以它能表示的最大数当然是8位都是1(既然2进制的数只能是01,如果是我们常见的10进制,那就8位都为9 1字节的二进制数中,最大的数:11111111 这个数的大小是多少呢?让我们来把它转换为十进制数。 无论是什么进制,都是左边是高位,右边是低位。10进制是我们非常习惯的计数方式,第一位代表有几个1(即几个100),第二位代表有几个10(即几个101),第三位代表有几个100(即有几个102),用小学课本上的说法就是:个位上的数表示几个1,十位上的数表示向个10,百位上的数表示几个100…… 同理可证,二进制数则是:第1位数表示几个20),第2位数表示几个221),第3位数表示几个422),第4位数表示向个8(23)…… 以前我们知道1个字节有8位,现在通过计算,我们又得知:1个字节可以表达的最大的数是255,也就是说表示0~255256个数。 那么两个字节(双字节数)呢?双字节共16位。 1111111111111111,这个数并不大,但长得有点眼晕,从现在起,我们要学会这样来表达二制数: 1111 1111 1111 1111,即每4位隔一空格。 双字节数最大值为: 1 * 215 + 1 *214 + 1* 213 + 1 * 212 + 1 * 211 + 1 * 210 + …… + 1 * 22 + 1 * 21 + 1* 20 = 65535 很自然,我们可以想到,一种数据类型允许的最大值,和它的位数有关。具体的计算方法方法是,如果它有n位,那么最大值就是: n位二进制数的最大值:1 * 2(n-1) + 1 * 2(n-2) + ... + 1 * 20 4.原码、补码和反码的进一步说明
在计算机里如何表示整数?
整数有无穷多个,在计算机里,通常我们只能表示出其中的一部分。假如我们用 个比特来表示一个整数。个比特有 个状态,个比特就有 2^n 个状态,把这 2^n 个状态的集合记为 A. 显然,用 A,我们可以与 个整数建立起一一对应。我们还希望 所表示的整数能够象整数那样地运算---整数,象整数那样运算,这是不是一句废话?数学中的整数相加,仍然是一个整数,但 里两个整数相加,我们却无法保证它们的和仍在 中,用代数的术语来讲,叫做 "不满足封闭性",这是个很坏的性质。
4.1补码
不过数学上有处理这个问题的成熟方案,如果我们能后退一步,让 表示的是模 |A| 的剩余类,则加法运算马上就封闭了。而且这个时候 不仅可以与 2^n 个整数对应起来,而且,在某种意义下,可以与整数环 对应起来。用代数的观点,这个 "某种意义"就是所谓的同态。
整数有两种封闭运算,一种是加法,另一种是乘法。作为模 2^n 的剩余类,也有加乘两种运算。定义 到 的映射 
f(x) = m mod 2^n
是一个同态,也就是说,满足这样的良好性质:
f(x+y) = f(x) + f(y) 
f(xy) = f(x)f(y)
我们通常使用 10 进制数,在这个进制下,f(x) 并不容易计算,但是在计算机里,本质的表示是二进制,于是 f(x) 的运算变得出奇地简单。如果 小于 2^n,则 的 进制表示就是 f(x),如果 x>=2^n,则要求其模 2^n 的余数,这恰好是 二进制表示的最低 位,换句话说,简单地把高位抛弃就行了。顺便指出,f(0)=0, f(1)=1.
我们来看一看 中的加法,f(x)+f(y), 若结果小于 2^n,则运算自然封闭,如果 f(x)+f(y) >= 2^n,则取其最低的 位,用电路实现时,可以简单地扔掉高位,保留低位。
到目前为止,一切都很好,但是减法怎么办呢?对整数运算而言,减去 不过是加上 的相反数的同义语。只要对 中的每个元素,能容易计算出其相反数就可以了。理论上 f(x) 的相反数就是
f(-f(x))
不过这个好像不容易计算,因为我们现在并没有给出 "负数"的概念,事实上 是模 2^n 的剩余类环,根本就没有所谓的负数。
这个困难也是容易处理的。作为 f(x) 的相反数,-f(x) 应该满足这样的性质:
-f(x) + f(x) = f(0) = 0
所以我们只要有在 中找一个元素,使得它与 f(x) 的和是 就可以了。但是 f(x) 本身可能含有很多比特 1,加上一个数能使它们变成 吗? 考虑到 中的加法要模去一个 2^n,这个问题实际上很好办,只要让求出的和是 2^n 就可以了。所以难发现 -f(x) 就是把 f(x) 的比特"取反"---即 变 1, 变 0,并加上 就得到了 -f(x)。容易验证:
f(-x) = - f(x).
现在我们回过头来看前面的一句话,红 {MOD}部分:
"我们通常使用 10 进制数,在这个进制下,f(x) 并不容易计算,但是在计算机里,本质的表示是二进制,于是 f(x) 的运算变得出奇地简单。如果 小于 2^n,则 的 进制表示就是 f(x),如果 x>=2^n,则要求其模 2^n 的余数,这恰好是 二进制表示的最低 位,换句话说,简单地把高位抛弃就行了。顺便指出,f(0)=0, f(1)=1."
红 {MOD}那句话简直是胡扯,因为没有考虑到负数的情况。但 f(x) 容易计算这句话并没有错,因为当 为负数时,我们可以利用 f(x) = -f(-x)。 由于 -x 是正的,f(-x) 容易计算,之后 -f(x) 也不过是取反加 而已。
好,到目前为止---在数学世界里,简直是完美的。但是回到现实, 里头真的没有负数,怎么办?整数能比较大小,里头的数又怎么办?
这时候,我们可以用一个不太完美的方案,把 里头的元素再映射回整数 中,如果只需要无符号的数,则变换为
g(u) = u,    0<= u <2^n-1
其实就是不把 中的元素模 2^n 的剩余类,而直接看成整数。不过这时的运算就要考虑溢出了。如果是无符号的情形,运算的结果仍是模 2^n 的余数,外加一个溢出标志。
如果要考虑负数,如果没有特别的理由,则要求正负个数大致相等是自然的。可以考虑让 代表 0, 1~2^{n-1}-1 代表正数,2^{n-1}+12^n-1 代表负数。丢掉一个 2^{n-1} 是因为希望正数和负数刚好配对,若 代表负数,则其落在 2^{n-1}+1~2^n-1 中,但它代表负几呢?当然是负 -u 了。从两个相反数之和为 0 ,或 2^n 的观点,-u 就是 2^n-u, 而 则是 -(-u) 即 u-2^n
2^{n-1} 是个麻烦,在 中,它的相反数就是自己,把它当正数,负数都不大合理。出于完整性考虑,随便选一个好了。在这里我们让 2^{n-1} 为负数,理由是它的最高位是 1,跟其它"负数"长得比较象。
换个角度看,模 的剩余类,或者余数为 0,1,...,2^{n-1}, 2^{n-1}+1, ..., 2^n-1. 这些余数也可以表示为:
0,1~2^{n-1}, 2^n-(2^{n-1}-1), ..., 2^{n}-1
我们刚才所做的就是把上面数列中 2^{n} -r 1<= r<= 2^{n-1}-1)类型的的数对应为负数 -r 而已。用代数的术语,就是在陪集中选取了不同的代表元。
于是我们定义 到 的变换,使得
当 0<= u <= 2^{n-1} 时, g(u) = u 
当 2^{n-1} 时,g(u)=u - 2^n
当然,这时运算起来有可能溢出,但结果仍可以看成模 2^n 的余数,外加一个溢出标记。
上面所说的就是补码。
4.2反码
抽象地看,反码与补码只有一个区别,同样的 比特状态集合 A,反码让这些元素表示模 2^n-1 的剩余类,而补码模 2^n。于是从 到 的映射定义为
f(x) = x mod 2^n-1
不过且慢,的大小是 2^n,而模 2^n-1 的余数只有 2^n-1 个? 是的,这是反码一个不完美的地方,它浪费了全 的码字,不过我们可以把全 的和全 的数看成一样的。
同样是个环同态,满足
f(x+y) = f(x) + f(y) 
f(xy) = f(x)f(y) 
俄且 f(0)=0, 或者全 1, f(1) = 1.
f(x) 可以计算如下,设 
x = 2^n q + r,
则由于 2^n = 1 mod (2^n-1),所以
x = q + r (mod 2^n -1)
若然 q+r 仍大于等于 2^n,则递归使用上述步骤。当然也可以直接去模 2^n-1,但上面的处理充分利用了二进制表示。而且当 为负数时,我们有两外的处理方法,见后面相反数的部分。
f(x) 能求出后,中的加法运算和乘法运算也就容易处理了,若 f(x)+f(y)<2^n,则不作特别处理,如果 f(x)+f(y) >=2^n,则结果为 f(f(x)+f(y)) 即把第 n+1 比特加到最低比特上。对于乘法,f(x)f(y) 可以多出n-1 位,处理方法是把第 n+i 比特加到第 比特上,或者说把高出 位的比特都右移 位并加在低位上如果仍然越界,则重复上述步骤。从这里可以看到反码的一个弱点,它的越界处理比较麻烦,而补码直接把越界的位丢掉就是了。
中的相反数如何求? -f(x) + f(x) = f(0) = 0, 加出一个全零比较难办,但加出一个全 不是问题,所以我们可以让 -f(x) 为 的取反,即 变成 1, 变成 0。于是求相反数的问题解决了。
同样,我们可以考虑在反码中引入正数,负数,这个与补码的类似,这里就不多说了。
有趣的是 2^n 几乎不可能是素数,而 2^n-1 有可能. 2^n-1 形的素数称为梅森素数。如果 2^n-1 是素的,则 中非零元素都可求逆。那么对应于 32 位和 64 位计算机的,将会是 31 位计算机和 61 位计算机,看上去很不错嘛,期待中。
4.3原码
这个没有什么可说的,2^n 个状态直接表示 02^n-1,如果要引入负数,则让最高位为 的表示负数所以 0x 代表 x,而 1x 代表 -x.
原码最好的地方是它简单,最不好的地方在于它没有良好的代数结构。 5.补码的原理及随想 
越早在课堂上学的东西,越给我以简单的印象,忘得也越快。而事实上,它们往往是最富有智慧的,即便在我没忘的时候,也没有深刻地理解它们。 
嘛是补码?不少书上扯一堆取反加1”之类的规则,很不着重点,我觉得核心在于: 
对于范围为[0,M)的整数计量系统,其模为M。和为M的两个数互为补数。 
如果有两个整数a,b∈[0, M),那么f(a-b)==f(a+c),其中c= M-b,是b的补码
f是一个映射,定义为: 
0 <=x < M,f(x)=x; 
x>= M,f(x)=x % M;-------->此时就要消除溢出 
x <0,f(x)=f(M +x). 
其中%为取余运算(效果同编程语言中的取模运算)。 
在计算机中,f是由溢出隐式实现的,所以天生就有a-b==a+c。这就把减运算转化成了加运算。 
于是,为了便于执行减运算,计算机就把-b表示为其补码c。 
假设机器字有n位,那么M=2nc=2n-b。 
人在纸上怎么计算2n-b的二进制值?2n的原码就是1后面跟了n0,直接用它减b的原码不方便,先用2n-1的原码(n1)减b的原码,得到的结果加上1就是2n-b的值了——这就是取反加1”的由来。 
6.有符号数,无符号数
同样是年纪和工资,前者不需要有负值,但后者可能需要——至少所有的老板都这样认为。那么,负数在计算机中如何表示呢?这一点,你可能听过两种不同的回答。 一种是教科书,它会告诉你:计算机用补码表示负数。可是有关补码的概念一说就得一节课,这一些我们需要在第6章中用一章的篇幅讲2进制的一切。再者,用补码表示负数,其实一种公式,公式的作用在于告诉你,想得问题的答案,应该如何计算。却并没有告诉你为什么用这个公式就可以和答案? 另一种是一些程序员告诉你的:用二进制数的最高位表示符号,最高位是0,表示正数,最高位是1,表示负数。这种说法本身没错,可是如果没有下文,那么它就是错的。至少它不能解释,为什么字符类型的-1用二进制表示是“1111 1111”(16进制为FF);而不是我们更能理解的“1000 0001”。(为什么说后者更好理解呢?因为既然说最高位是1时表示负数,那1000 0001不是正好是-1吗?)。 让我们从头说起。 6.1你自已决定是否需要有正负。 就像我们必须决定某个量使用整数还是实数,使用多大的范围数一样,我们必须自已决定某个量是否需要正负。如果这个量不会有负值,那么我们可以定它为带正负的类型。 在计算机中,可以区分正负的类型,称为有符类型,无正负的类型(只有正值),称为无符类型。 数值类型分为整型或实型,其中整型又分为无符类型或有符类型,而实型则只有符类型。 字符类型也分为有符和无符类型。 比如有两个量,年龄和库存,我们可以定前者为无符的字符类型,后者定为有符的整数类型。 6.2使用二制数中的最高位表示正负。 首先得知道最高位是哪一位?1个字节的类型,如字符类型,最高位是第7位,2个字节的数,最高位是第15位,4个字节的数,最高位是第31位。不同长度的数值类型,其最高位也就不同,但总是最左边的那位(如下示意)。字符类型固定是1个字节,所以最高位总是第7位。 (红 {MOD}为最高位) 单字节数: 1111 1111 双字节数: 1111 1111 1111 1111 四字节数: 1111 1111 1111 1111 1111 1111 1111 1111 当我们指定一个数量是