计算机组成(二):数据在计算机中的表示

我们都知道,计算机的核心是CPU,而CPU就是运算器和控制器,其作用就是进行数学运算。

想要理解CPU工作的原理,就要从两方面着手,一方面是计算机中的数据是如何表示的,另一方面就是在这种表示方式下,它的运算规则又是怎样的,计算机如何用电路去表示这种运算。

我们首先来了解下计算机中数据的表示:

在计算系统内部,所有的信息都是用二进制编码的,主要原因有以下几点:

  • 二进制只有两种状态,使用有两个稳定状态的物理器件就可以表示二进制数的每一位,成本低。
  • 二进制的1和0正好与逻辑值的“真”和“假”对应,为计算机实现逻辑运算和程序中的逻辑判断提供了便利。
  • 二进制编码的运算规则简单,通过逻辑门电路就能方便地实现算术运算。

进位计数法

每个数码所表示的数值等于该数码本身乘以一个与他所在数位有关的常数,这个常数叫做位权。一个进位数的数值大小就是它的各位数吗按权相加,一个r进制数的数值可以表示为:

Knrn+Kn1rn1+...+K0r0+K1r1+...+Kmrm=i=nmKiriK_nr^n + K_{n-1}r^{n-1}+ ... + K_0r^0 + K_{-1}r_{-1} + ... + K_{-m}r^{-m} = \sum_{i=n}^{-m}K_ir^i

基于这个公式,我们就可以得出不同进制之间的转换公式。

我们常用的进制有二进制,八进制,十进制和十六进制。

其中二进制,八进制和十六进制,都是2的整数幂,他们之间的转换有更为简单的方式,而这三种进制与十进制之间的转换就要通过上面的数值表示方式来推导。

首先是二进制,八进制,和十六进制之间的转换,其实都是通过二进制位桥梁进行,一位八进制可以由三位二进制数表示,而一位16进制数可以由4位二进制数表示,而八进制和十六进制之间转换需要先转换为二进制。

任意进制转换为十进制,只需要各位数码与位权相乘就行,其实就是上面的公式,只不过该公式左边是一串数字相加,而右边虽然表示的是数量,用多少进制都可以,但我们习惯用十进制来表示。

而十进制转换为任意进制,则分为两种情况了:

  • 对于整数部分,采用除基取余法。最先取得的余数位数的最低位,最后的取的余数位数的最高位,商位0时结束。
  • 对于小数部分,采用乘基取整法。小数部分乘基取整,最先取得的整数为数的最高位,最后取得的整数为数的最低位,乘积为1.0时或者达到精度要求时停止。

这里可能会有一定疑惑,为什么整数部分除法,小数部分除法,这可以通过结合我们刚才的公式看出来,我们对刚才公式的整数部分

Knrn+Kn1rn1+...+K0r0r\frac {K_nr^n + K_{n-1}r^{n-1}+ ... + K_0r^0} r

k0就是余数,也就是我们得到的第一个余数,应该是最低位,然后再除以r,余数就是k1,继续除下去,就会得到kn,所以最后得到的余数kn就是最高位。

而对于小数部分,我们想得到一个位权为r^0的数码,我们就要不断去乘以r

定点数的编码表示

根据小数的位置是否固定,在计算机中有两种数据格式:定点表示和浮点表示。

在计算机中,通常用定点补码整数表示整数,定点原码小数表示浮点数的位数部分,用移码表示浮点数的阶码部分

机器数的定点表示

定点表示法用来表示定点小数和定点整数:

  • 定点小数。定点小数是纯小数,约定小数点在符号位之后,有效数值部分最高位之前。若数据X的形式为X=x0.x1x2...xnX = x_0.x_1x_2...x_n ,其中x0x_0为符号位,x1 xnx_1~x_n为数值的有效部分。
  • 定点整数。定点整数为纯整数,约定小数点在有效数值部分最低位之后若数据X的形式为X=x0x1x2...xnX = x_0x_1x_2...x_n ,其中x0x_0为符号位,x1 xnx_1~x_n为数值的有效部分。

注意,无论是定点小数还是定点整数,无论是有符号数还是无符号数,在寄存器中都是一串二进制而已,也就是机器数,而具体怎么解读这串二进制那是程序的事情,比如如果类型是unsignint,那就是用无符号整数方式去解读。

也正是因为这个,无论整数小数,有符号无符号都是一样的运算方式,所以我们提出了补码,因为只有这种方式的表示方法才可以做到这一点。

原码,反码,补码和移码

首先给出几个比较容易混淆的结论:

  • 正数的原码,反码,补码都是一样的,注意正负数进行补码运算的时候,不要把负数转换补码的方式用到正数上。
  • 正数的原码和移码不同。
  • 既然说到负数了,那么就肯定是有符号数,就不要讨论无符号数了。
  • 正数不等于无符号数,只不过无符号数的原码反码补码都相同。

个人猜测,在数据进入加法器之前,有一件事是必须要做的,如果不是程序编译器做,那就是有专门的电路做,就是如果是有符号的负数,那就转换成补码进入加法器,无符号数和有符号正数直接原码进入加法器。有专门的电路做这件事感觉可能性更高。

原码

用机器数的最高位表示数的符号,其余各位表示数的绝对值。原码的定义如下:

纯小数的原码定义:

[x]={x1>x01x=1+x0>x>1[x]_{原} = \begin{cases} x & 1\gt x\ge0\\ 1 - x = 1 + |x| & 0 \gt x\gt-1 \end{cases}

x是真值,[x][x]_{原}是原码机器数。

x1=+0.1101,x2=0.1101x_1 = +0.1101, x_2 = -0.1101 ,字长是8位,则其原码表示为:[x1]=0.1101000,[x2]=1(0.1101)=1.1101000[x_1]_原=0.1101000, [x_2]_原=1-(-0.1101)=1.1101000,其中最高位为符号位

这里其实还隐含了另外一个知识点:原码补位补的是0

若字长为n+1,那么原码小数的表示范围为:(12n)x12n-(1-2^{-n})\le x \le 1 - 2^{-n},关于原点对称。

纯整数的原码定义:

[x]={0,x2n>x02nx=2n+x0>x>2n[x]_{原} = \begin{cases} 0,x & 2^n\gt x\ge0\\ 2^n - x = 2^n + |x| & 0 \gt x\gt-2^n \end{cases}

n+1位原码正数的表示范围是:(2n1)x2n1-(2^n-1)\le x \le 2^n-1,关于原点对称

对于原码来说,有正0和负0两种表示方法,即+0=00000,-0=10000.

补码

原码加减运算规则比较复杂,对于两个不同符号数的加法(或同符号数的减法),先要比较两个数的绝对值大小,然后用绝对值大的去减去绝对值小的数,最后还要给结果选择合适的符号,比较麻烦,而补码的加减运算则是统一的。

纯小数的补码定义:

[x]={x1>x02+x=2x0>x1 mod 2[x]_{补} = \begin{cases} x & 1\gt x\ge0\\ 2 + x = 2 - |x| & 0 \gt x\ge-1 \end{cases} \ mod \ 2

纯整数的补码定义:

[x]={0,x2n>x02n+1+x=2n+1x0>x2n mod 2n+1[x]_{补} = \begin{cases} 0,x & 2^n\gt x\ge0\\ 2^{n+1} + x = 2^{n+1} - |x| & 0 \gt x\ge-2^n \end{cases} \ mod\ 2^{n+1}

补码的表示数量比原码多了一位,就是最小能多表示一个数,原因是补码的正0和负0是一样的,于是我们就定义原本的-0为2n-2^n

补码是最重要的表示方式,我们首先要理解,他为什么叫做补码。因为原码和补码之间的数量关系是互补的。对于负数来说,对应正数的原码+补码等于2n+12^{n+1}

至于后面那个模2则是计算机的一种天然的运算,机器数一共8位,也就是n=7的时候,如果加上282^8,就是最高位之前多了个1,但是这一位其实是会溢出的,有相当于模了2n+12^{n+1}

那么我们为什么要绕这么一大圈呢?

我们用纯整数的定义来看,假设机器数长位8位,也就是上面公式的n=7,有一个负数-9,我们平时计算126加-9,可以是就是直接算,那就是117。

但是如果它加上2的8次幂(也就是256),那就是256-9 = 247,我们就把126-9表示位(126+247) mod 256,还是117

这样绕了一圈之后,我们就可以把减法也表示为加法了。

当x是负数时,补码为2n+1+x mod 2n+12^{n+1} +x\ mod\ 2^{n+1}是数值上的表示,表明在计算上的结果是正确的,而实际我们求取补码是通过2n+1x2^{n+1}-|x|

反码

反码比较简单,也没什么太大意义,他只是原码变为补码的一种中间态,原码除符号位取反就是反码,反码加1就是补码。

注意,上面这一套是对负数的,正数反码和原码一样。

移码

移码的定义为:

[x=2n+x][x_{移} = 2 ^ n + x]

他叫移码,其实就是相当于整体把原本在数轴上对称的原码表示,整体右移到数轴的正数部分,它的作用就是,移码逐位比较,大的就是大,不像有符号数,第一位0是比1小,但其实0是正号。

而且有一点比较有意思,移码和补码码除了符号为之外,是正好相反的。
也就是说,原码转反码的过程和补码转移码的过程是一样的,都是符号为不变,其他为取反。