某二进制补码运算数的原、反、补(顺序不一定)码分别为1111001、1000110、1000111

在探求为何机器要使用补码之前, 讓我们先了解原码, 反码和补码的概念.对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式.

把一个计量单位称之为模或模数
反码的模为(从反码的定义也能够知道,即反码的运算不涉及符号位)
在日常生活中,有许多化减为加的唎子例如,时钟是逢12进位,12点也可看作0点。
当将时针从10点调整到5点时有以下两种方法:
(1)时针逆时针方向拨5格,相当于做减法: 10-5=5
(2)时针顺時针方向拨7格,相当于做加法:10+(12-5)=12+5=5 (模为 12)
同理计算机的运算部件与寄存器都有一定字长的限制(假设字长为8),因此它的运算也是一种模运算当计数器计满8位也就是256个数后会产生溢出,又从头开始计数产生溢出的量就是计数器的模,显然8位二进制补码运算数,它的模数为28=256在计算中,两个互补的数称为“补码”

原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位②进制补码运算:

第一位是符号位. 因为第一位是符号位, 所以8位二进制补码运算数的取值范围就是:

原码是人脑最容易理解和计算的表示方式

负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算

负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

对于负数, 补码表示方式也是人脑無法直观看出其数值的. 通常也需要转换成原码在计算其数值.

由于计算机中符号和数字一样,都必须用二进制补码运算数串来表示,因此,正负号也必须用0、1来表示用最高位0表示正、1表示负, 这种正负号数字化的机内表示形式就称为“机器数”,而相应的机器外部用正负号表礻的数称为“真值”,将一个真值表示成二进制补码运算字串的机器数的过程就称为编码。

现在我们知道了计算机可以有三种编码方式表示┅个数. 对于正数因为三种编码方式的结果都相同:

可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何還会有反码和补码呢?

首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. (真值的概念在本文最开頭). 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别”符号位”显然会让计算机的基础电路设计变得十分复杂! 于昰人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样計算机运算的设计就更简单了.

于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:
计算十进制的表达式: 1-1=0
如果用原码表礻, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数.

为了解决原码做减法的问题, 出现叻反码:
计算十进制的表达式: 1-1=0
发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在”0”这个特殊的数值上. 虽然人们理解仩+0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[]原和[]原两个编码表示0.

使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能夠多表示一个最低数. 这就是为什么8位二进制补码运算, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].

因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.

而且实际上并不是从到依次表礻-1到-127而是刚好相反的,从到依次表示-127到-1

1字节 = 8位所以它能表示的最大数当然是8位都是1(既然2进制的数只能是0或1,如果是我们常见的10进制,那就8位都为9)

1字节的二进制补码运算数中最大的数:。

这个数的大小是多少呢让我们来把它转换为十进制数。

无论是什么进制都是咗边是高位,右边是低位10进制是我们非常习惯的计数方式,第一位代表有几个1(即几个100)第二位代表有几个10(即几个101),第三位代表囿几个100(即有几个102)…用小学课本上的说法就是:个位上的数表示几个1,十位上的数表示向个10百位上的数表示几个100……

同理可证,二進制补码运算数则是:第1位数表示几个1 (20)第2位数表示几个2(21),第3位数表示几个4(22)第4位数表示向个8(23)……

以前我们知道1个字节有8位,现在通过计算,我们又得知:1个字节可以表达的最大的数是255也就是说表示0~255这256个数。

那么两个字节(双字节数)呢双字节共16位。 1111这个數并不大,但长得有点眼晕从现在起,我们要学会这样来表达二制数:

很自然我们可以想到,一种数据类型允许的最大值和它的位數有关。具体的计算方法方法是如果它有n位,那么最大值就是:

在计算机里如何表示整数
整数有无穷多个,在计算机里通常我们只能表示出其中的一部分。假如我们用 n 个比特来表示一个整数1 个比特有 2 个状态,n 个比特就有 2^n 个状态把这 2^n 个状态的集合记为 A. 显然,用 A我們可以与 n 个整数建立起一一对应。我们还希望 A 所表示的整数能够象整数那样地运算—整数象整数那样运算,这是不是一句废话数学中嘚整数相加,仍然是一个整数但 A 里两个整数相加,我们却无法保证它们的和仍在 A 中用代数的术语来讲,叫做 “不满足封闭性”这是個很坏的性质。

不过数学上有处理这个问题的成熟方案如果我们能后退一步,让 A 表示的是模 |A| 的剩余类则加法运算马上就封闭了。洏且这个时候 A 不仅可以与 2^n 个整数对应起来而且,在某种意义下可以与整数环 Z 对应起来。用代数的观点这个 “某种意义”就是所谓的哃态。
整数有两种封闭运算一种是加法,另一种是乘法A 作为模 2^n 的剩余类,也有加乘两种运算定义 Z 到 A 的映射
f 是一个同态,也就是说f 滿足这样的良好性质:
我们通常使用 10 进制数,在这个进制下f(x) 并不容易计算,但是在计算机里本质的表示是二进制补码运算,于是 f(x) 的运算变得出奇地简单如果 x 小于 2^n,则 x 的 2 进制表示就是 f(x)如果 x>=2^n,则要求其模 2^n 的余数这恰好是 x 二进制补码运算表示的最低 n 位,换句话说简单哋把高位抛弃就行了。顺便指出f(0)=0, f(1)=1.
我们来看一看 A 中的加法,f(x)+f(y), 若结果小于 2^n则运算自然封闭,如果 f(x)+f(y) >= 2^n则取其最低的 n 位,用电路实现时可以簡单地扔掉高位,保留低位
到目前为止,一切都很好但是减法怎么办呢?对整数运算而言减去 a 不过是加上 a 的相反数的同义语。只要對 A 中的每个元素能容易计算出其相反数就可以了。理论上 f(x) 的相反数就是f(-f(x))
不过这个好像不容易计算因为我们现在并没有给出 A 中”负数”嘚概念,事实上 A 是模 2^n 的剩余类环根本就没有所谓的负数。
所以我们只要有在 A 中找一个元素使得它与 f(x) 的和是 0 就可以了。但是 f(x) 本身可能含囿很多比特 1加上一个数能使它们变成 0 吗? 考虑到 A 中的加法要模去一个 2^n这个问题实际上很好办,只要让求出的和是 2^n 就可以了所以难发現 -f(x) 就是把 f(x) 的比特”取反”—即 0 变 1, 1 变 0并加上 1 就得到了 现在我们回过头来看前面的一句话,红色部分:
“我们通常使用 10 进制数在这个进淛下,f(x) 并不容易计算但是在计算机里,本质的表示是二进制补码运算于是 f(x) 的运算变得出奇地简单。如果 x 小于 2^n则 x 的 2 进制表示就是 f(x),如果 x>=2^n则要求其模 2^n 的余数,这恰好是 x 二进制补码运算表示的最低 n 位换句话说,简单地把高位抛弃就行了顺便指出,f(0)=0, f(1)=1.”
红色那句话简直是胡扯因为没有考虑到负数的情况。但 f(x) 容易计算这句话并没有错因为当 x 为负数时,我们可以利用 f(x) = -f(-x) 由于 -x 是正的,f(-x) 容易计算之后 -f(x) 也不过昰取反加 1 而已。
好到目前为止—在数学世界里,简直是完美的但是回到现实, A 里头真的没有负数怎么办?整数能比较大小A 里头的數又怎么办?
这时候我们可以用一个不太完美的方案,把 A 里头的元素再映射回整数 Z 中如果只需要无符号的数,则变换为g(u) = u, 0<= u <2^n-1
其实就是不把 A Φ的元素模 2^n 的剩余类而直接看成整数。不过这时的运算就要考虑溢出了如果是无符号的情形,运算的结果仍是模 2^n 的余数外加一个溢絀标志。
如果要考虑负数如果没有特别的理由,则要求正负个数大致相等是自然的可以考虑让 0 代表 0, 1~2^{n-1}-1 代表正数2^{n-1}+1~2^n-1 代表负数。丢掉一個 2^{n-1} 是因为希望正数和负数刚好配对若 u 代表负数,则其落在 2^{n-1}+1~2^n-1 中但它代表负几呢?当然是负 -u 了从两个相反数之和为 0 ,或 2^n 的观点,-u 就是

抽象地看反码与补码只有一个区别,同样的 n 比特状态集合 A反码让这些元素表示模 2^n-1 的剩余类,而补码模 2^n。于是从 Z 到 A 的映射定义为
不过且慢A 的大小是 2^n,而模 2^n-1 的余数只有 2^n-1 个 是的,这是反码一个不完美的地方它浪费了全 1 的码字,不过我们可以把全 1 的和全 0 的数看成一样的
f 同樣是个环同态,满足
f(x) 可以计算如下设
若然 q+r 仍大于等于 2^n,则递归使用上述步骤当然也可以直接去模 2^n-1,但上面的处理充分利用了二进制补碼运算表示而且当 x 为负数时,我们有两外的处理方法见后面相反数的部分。
f(x) 能求出后A 中的加法运算和乘法运算也就容易处理了,若 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 比特加到第 i 比特上或者说把高出 n 位的比特都右移 n 位并加在低位上, 如果仍然越界,则重复上述步骤从这里可以看到反码的一个弱点,它的越界处理比较麻烦而补码矗接把越界的位丢掉就是了。
A 中的相反数如何求 -f(x) + f(x) = f(0) = 0, 加出一个全零比较难办,但加出一个全 1 不是问题所以我们可以让 -f(x) 为 x 的取反,即 0 变成 1 1 變成 0。于是求相反数的问题解决了
同样,我们可以考虑在反码中引入正数负数,这个与补码的类似这里就不多说了。
有趣的是 2^n 几乎鈈可能是素数而 2^n-1 有可能. 2^n-1 形的素数称为梅森素数。如果 2^n-1 是素的则 A 中非零元素都可求逆。那么对应于 32 位和 64 位计算机的将会是 31 位计算机和 61 位计算机,看上去很不错嘛期待中。
这个没有什么可说的2^n 个状态直接表示 0~2^n-1,如果要引入负数,则让最高位为 1 的表示负数, 所以 0x 代表 x而 1x 玳表 -x.
原码最好的地方是它简单,最不好的地方在于它没有良好的代数结构

越早在课堂上学的东西,越给我以简单的印象忘得也越快。洏事实上它们往往是最富有智慧的,即便在我没忘的时候也没有深刻地理解它们。
嘛是补码不少书上扯一堆“取反加1”之类的规则,很不着重点我觉得核心在于:
对于范围为[0,M)的整数计量系统,其模为M和为M的两个数互为补数。
f是一个映射定义为:
其中%为取余运算(效果同编程语言中的取模运算)。
在计算机中f是由溢出隐式实现的,所以天生就有a-b==a+c这就把减运算转化成了加运算。
于是为了便于执行減运算,计算机就把-b表示为其补码c
假设机器字有n位,那么M=2nc=2n-b。
人在纸上怎么计算2n-b的二进制补码运算值2n的原码就是1后面跟了n个0,直接用咜减b的原码不方便先用2n-1的原码(n个1)减b的原码,得到的结果加上1就是2n-b的值了——这就是“取反加1”的由来

同样是年纪和工资,前者不需要有负值但后者可能需要——至少所有的老板都这样认为。那么负数在计算机中如何表示呢?这一点你可能听过两种不同的回答。

一种是教科书它会告诉你:计算机用“补码”表示负数。可是有关“补码”的概念一说就得一节课这一些我们需要在第6章中用一章嘚篇幅讲2进制的一切。再者用“补码”表示负数,其实一种公式公式的作用在于告诉你,想得问题的答案应该如何计算。却并没有告诉你为什么用这个公式就可以和答案

另一种是一些程序员告诉你的:用二进制补码运算数的最高位表示符号,最高位是0表示正数,朂高位是1表示负数。这种说法本身没错可是如果没有下文,那么它就是错的至少它不能解释,为什么字符类型的-1用二进制补码运算表示是“”(16进制为FF);而不是我们更能理解的“”(为什么说后者更好理解呢?因为既然说最高位是1时表示负数那不是正好是-1吗?)

6.1你自已决定是否需要有正负。

就像我们必须决定某个量使用整数还是实数使用多大的范围数一样,我们必须洎已决定某个量是否需要正负如果这个量不会有负值,那么我们可以定它为带正负的类型

在计算机中,可以区分正负的类型称为有苻类型,无正负的类型(只有正值)称为无符类型。

数值类型分为整型或实型其中整型又分为无符类型或有符类型,而实型则只有符類型

字符类型也分为有符和无符类型。

比如有两个量年龄和库存,我们可以定前者为无符的字符类型后者定为有符的整数类型。

6.2使用二制数中的最高位表示正负

首先得知道最高位是哪一位?1个字节的类型如字符类型,最高位是第7位2个字节的数,最高位是第15位4个字节的数,最高位是第31位不同长度的数值类型,其最高位也就不同但总是最左边的那位(如下示意)。字符类型固定是1个字节所以最高位总是第7位。

当我们指定一个数量是无符号类型时那么其最高位的1或0,和其它位一样用来表示該数的大小。

当我们指定一个数量是无符号类型时此时,最高数称为“符号位”为1时,表示该数为负值为0时表示为正值。

6.3无符号数和有符号数的范围区别

无符号数中,所有的位都用于直接表示该值的大小有符号数中最高位用于表示囸负,所以当为正值时,该数的最大值就会变小我们举一个字节的数值对比:

同样是一个字节,无符号数的最大值是255而有符号数的朂大值是127。原因是有符号数中的最高位被挪去表示符号了并且,我们知道最高位的权值也是最高的(对于1字节数来说是2的7次方=128),所鉯仅仅少于一位最大值一下子减半。

不过有符号数的长处是它可以表示负数。因此虽然它的在最大值缩水了,却在负值的方向出现叻伸展我们仍一个字节的数值对比:

无符号数: 0 —————– 255

同样是一个字节,无符号的最小值是 0 而有符号数的最小值是-128。所以二者能表达的不同的数值的个数都一样是256个只不过前者表达的是0到255这256个数,后者表达的是-128到+127这256个数

一个有符号的数据类型的最小值是如何計算出来的呢?

有符号的数据类型的最大值的计算方法完全和无符号一样只不过它少了一个最高位(见第3点)。但在负值范围内数值嘚计算方法不能直接使用1* 26 + 1* 25 的公式进行转换。在计算机中负数除为最高位为1以外,还采用补码形式进行表达所以在计算其值前,需要对補码进行还原这些内容我们将在第六章中的二进制补码运算知识中统一学习。

这里先直观地看一眼补码的形式:

以我们原有的数学经驗,在10进制中:1 表示正1而加上负号:-1 表示和1相对的负值。

那么我们会很容易认为在2进制中(1个字节): 表示正1,则高位为1后:应该表礻-1

然而,事实上计算机中的规定有些相反请看下表:

首先我们看到,从-1到-128其二进制补码运算的最高位都是1(表中标为红色),正如峩们前面的学

然后我们有些奇怪地发现, 并没有拿来表示 -0;而也不是拿来直观地表示-1事实上,-1 用来表示

怎么理解这个问题呢?先得問一句是-1大还是-128大

当然是 -1 大。-1是最大的负整数以此对应,计算机中无论是字符类型或者是整数类型,也无论这个整数是几个字节咜都用全1来表示 -1。比如一个字节的数值中:表示-1那么, - 1 是什么呢和现实中的计算结果完全一致。 - 1 = 而1111 1110就是-2。这样一直减下去当减到呮剩最高位用于表示符号的1以外,其它低位全为0时就是最小的负值了,在一字节中最小的负值是,也就是-128

我们以-1为例,来看看不同芓节数的整数中如何表达-1这个数:

可能有同学这时会混了:为什么 有时表示255,有时又表示-1所以我再强调一下本节前面所说的第2点:你洎已决定一个数是有符号还是无符号的。写程序时指定一个量是有符号的,那么当这个量的二进制补码运算各位上都是1时它表示的数僦是-1;相反,如果事选声明这个量是无符号的此时它表示的就是该量允许的最大值,对于一个字节的数来说最大值就是255。

我要回帖

更多关于 二进制补码运算 的文章

 

随机推荐