计算机组成原理-04-数据的表示

计算机组成原理-04-数据的表示

前言

  • 本篇内容多涉及计算,内容较为硬核,请耐心观看

mark

1. 定点数和浮点数的区别

  • 根据小数点的位置是否固定,在计算机内有两种数据格式

    • 定点数
    • 浮点数
  • 定点表示:约定机器数中的小数点位置固定不变,小数点不再用“,”表示,而是约定了它的位置

2. 无符号数和有符号数

  • 无符号数:指整个机器字长的全部二进制位均为数值位,没有符号位。若机器字长为8位,则数的表示范围 0~2^8-1 , 即0~255
    • 无符号数一般只代表整数,不代表小数
  • 有符号数:在机器中,数的正负我们无法识别,但是我们可以用二进制数来代替正负号。一般‘0’为正,‘1’为负,符号位一般在有效数的最前面。若机器字长为8位,是有符号数,则数的表示范围为 -2^72^7-1 ,即-128127。

3. 定点数的表示

  • 定点小数:一般将小数点的位置固定在数据的最高位之前
  • 定点整数:一般将小数点的位置固定在数据的最低位之后

mark

3.1 原码

  • 用机器数的最高位来代表该数的符号,其余的各位表示数的绝对值

mark

3.2 反码

mark

  • 反码通常用于原码求补码的中间过渡
  • 反码
    • 若符号位为0,则反码和原码相同
    • 若符号位为1,则数值位全部取反

mark

3.3 补码

mark

  • 正数的补码和原码相同
  • 负数的补码
    • 先求出反码
    • 反码的基础上+1(需要注意进位的问题)

mark

  • 补码如何求原码?
    • 正数的补码和原码一样
    • 负数 :数值位全部取反,然后+1

3.4 移码

  • 移码的特性:非常方便比较大小
  • 只能用于表示整数

mark

mark

3.5 例题小结

mark

  • 如何从[x]补 求得 [-x]补
    • 技巧:符号位 数值位 全部取反
    • 并且末尾+1
  • 转换技巧小结

mark

4. 补码的作用和由来

  • 原因:原码的加减法和无符号的加减法的差异
    • 因此需要涉及一种算法:用加法代替减法

mark

  • 模m计算法则(详情请学习离散数学)
    • 如采用时钟,则相当于对12取模(-3和9的地位是等价的)
    • (mod 12)相当于把所有的整数分为了12类(余数为0-11)
    • mod12 属于相同的数,都是属于同一类,是等价的
    • 图示如下:这样就相当于把-3 改为了 +9等等(加法代替了减法)
    • 这里的 -3 和 9 互为补数,他们的绝对值之和是等于模的(12)

mark

  • 如计算机的机器字长是8bit,那么超出这个范围的话,只保留8为,那么就相当于mod 2^8 的条件
    • 那么补数 = 2^8 - 负数的绝对值
    • 补数 就是 补码的原生定义
    • 针对上个例子如图所示

mark

mark

  • 再来看个例子
    • 补码的加减法:符号位一起加入计算
    • 注意:加法会有溢出的产生

mark

5. 定点数的移位运算

mark

5.1 算数移位

5.1.1 原码的移位规则

  1. 整数的算数右移
    • 相当于除以2的操作
    • 高位补0,低位舍弃
    • 若舍弃的位不等于0,那么会造成精度的丢失

mark

  1. 整数的算数左移
    • 相当于乘以2的规则
    • 低位补0,高位舍弃
    • 若舍弃的位不等于0,则会造成严重的精度错误

mark

  1. 小数的原理同整数

mark

5.1.2 反码的算数移位

mark

  1. 正数的反码移位
  • 同原码
  1. 负数的反码移位
  • 右移除2,高位补1,低位舍弃
  • 左移乘2,低位补1,高位舍弃
  • 因为原码和反码的0和1互为相反

5.1.3 补码的算数移位

mark

  1. 正数的反码移位
  • 同原码
  1. 负数的反码移位
  • 右移除2,补位同反码 ,高位补1,低位舍弃
  • 左移乘2,补码同原码 ,低位补0,高位舍弃
  • 负数补码 = 反码末尾+1
    • 导致反码最右边几个连续的1都因进位而变为0,直到进位碰到第一个0为止
  • 规律 : 负数补码中,最右边的1及其右边同原码,最右边的左边同反码

5.1.4 算数移位小结

mark

5.2 逻辑移位

  • 看作对无符号数的算数移位
  • 左移右移都补0

mark

  1. RGB的应用
  • 用3B来存储RGB的值
  • 不断的右移就是RGB对应的机器码

mark

5.3 循环移位

mark

  • 对于加法产生的进位超过数据字长
    • 计算机采用进位位(CF)来记录进位值的大小
    • 具体左移右移如下所示
    • 作用:适合将数据的高位和低位进行调换(大端存储和小端存储)

mark

5.4 小结

mark

6. 定点数的溢出

mark

发生的原因及分类

  • 上溢:两个正数相加得到一个负数
  • 下溢:两个负数相加得到一个正数

举例描述

  • 三位二进制位的原码加法

mark

6.1 原码加法的规则

mark

6.2 采用一位符号位

mark

  • 符合规律
    • 正 + 正 = 负
    • 负 + 负 = 正

6.3 采用一位符号位并判断进位

mark

  • 符号位的进位和最高数位的进位C1

    • 相同则说明没有溢出
    • 否则表示发生了溢出
  • 计算机可以简单的通过异或两个进位的值运算得到是否产生溢出

6.4 采用双符号位

  • 双符号位又叫做模4补码
  • 正数的符号位是00
  • 负数的符号位是11

结果规则:

  • 符号位00 :结果为正 无溢出
  • 符号位11 : 结果为负 无溢出
  • 10 : 1表示原来的应该是负的 结果却是正的
  • 01 : 0表示原来的应该是正的 结果却是负的

举个例子:

mark

注意:

  • 使用双符号位:并不是有两个符号位,而是机器复制了一个符号位,这样并不会造成数据的扩展

6.5 溢出的原因

  • 目前的计算结果超出了机器字长所能表示的范围

符号扩展

  • 正整数的扩展
    • 在符号位后的高位补上0
  • 负整数的扩展
    • 原码:在符号位和数值位之间补0
    • 反码:在符号位和数值位之间补1
    • 补码:在符号位和数值位之间补1
      • 找到最右边的1
      • 右半部分同原码
      • 左半部分同反码

mark

小数的扩展

  • 正小数
    • 在尾部添加0
  • 负小数
    • 原码 :在末尾添0
    • 反码 : 在末尾添1
    • 补码 : 在末尾添0
      • 找到最右边的1
      • 右半部分同原码
      • 左半部分同反码

mark

7. 定点数的乘/除法原理

7.1 乘法原理

  1. 首先看看10进制乘法原理

mark

  1. 二进制的原码乘法

mark

  1. 以上是手算的方式,如果考虑用计算机如何实现?
    • 实际的数字有正负,符号位如何处理
    • 乘积的位数扩大一倍,如何处理?
    • 4个位积都要保存下来,最后统一相加?

7.1.1 原码一位乘法计算

  • 首先回忆一下运算器的组成

mark

mark

  • 对于乘法而言(实现方法:先加法再移位 重复n次)

mark

  • 原码一位乘法计算方法:

mark

举个例子

mark

mark

mark

  • 手算做题过程:采用双符号位

mark

  • 本质 : n 轮的加法和逻辑右移

7.1.2 补码的一位乘法(Booth 算法)

  • 计算方式

mark

  • 所谓的辅助位:初始化就是在最后加了一位0

  • 实现方式: n 轮加法 和 补码的算数移位(最后还需多一轮加法,符号位)

举个例子 : 需要采用双符号位

mark

  • 手算过程 : 双符号位

mark

7.1.3 小结

mark

7.2 除法原理

mark

  • 首先来看看十进制除法(思想:尽可能拼凑出余数)

mark

  • 手算二进制除法举例
mark
  • 再来回顾运算器的组成

mark

mark

7.2.1 原码的一位除法(恢复余数法)

  • 符号单独用异或来确定。
  • 计算机会默认上商1,如果搞错了再商0,并且恢复余数。

计算方式如下所示:

  • 数值位为 n , 逻辑左移需要作n 次
  • 加法 n + 1次

mark

举个例子

mark

mark

mark

  • 恢复余数法(手算)

mark

7.2.2 原码的一位除法(不恢复余数法)

计算方法如下

mark

  • 上面那个例子,中间过程推导一下,如下图所示:

mark

  • 操作合并为 :(余数*2 + 除数)

mark

mark

小结:

  • 加/ 减 共 n + 1 次,每次加减确定一位商
  • 左移 n 次,最后一次加减完不移位
  • 最后因为余数可能为负,还需要一次的恢复余数

7.2.3 补码的一位除法运算

补码除法

  • 符号位参与运算
  • 被除数/余数,除数采用双符号位

规则如下

mark

举个例子

mark

注意

  • 末位的商恒为1,这样带来的误差是非常小的
  • 补码商最后一步,不需要恢复余数

mark

除法小结

mark

8. C语言中的强制类型转换

  • 前提 : C语言中的定点整数使用补码进行表示的
  • 注意:数值范围没有负数的则为无符号位的数,有负数范围的则为有符号位的数

mark

1
2
3
4
5
6
输出时:
%u无符号十进制整数
%d有符号十进制整数
%0无符号八进制
%x无符号十六进制整数
在计算机中数据都是以补码形式存储的,%u无符号输出,没有符号位,是正数;%d有符号输出,有符号位,符号位在最高位。

8.1 有符号和无符号的转换

  • C语言中常利用强制类型转换,有时候强制类型转换的结果却不是我们希望得到的,因为计算机存储数据是以补码形式存储的。
  • 有时候强制类型转换可能会改变数值,可能是数据类型有无符号位导致的。
    • 无符号位是正数就不用转换,因为正数原码=补码
    • 有符号位的数,根据符号位是0还是1来判断是否需要转换,那么如果符号位是1,是负数就要转换,数值当然会不同。
1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
short x=-4321;
unsigned short y=(unsigned short)x;
printf("x=%d,y=%u\n",x,y);
return 0;
}

结果如下:

mark

  • 可以看出y值和x值没有一点关系,咱们将其都转换成二进制,我们便知道了为什么。且看二进制转换表;

mark

  • 由于电脑是64位,所以转换就是64个二进制数,至于为什么16位往左所有数都是1,这就涉及到了符号扩展

mark

小结:

  • 可以看出,强制类型转换结果相应位置的值不变,仅仅只改变了解释这些位的方式,是short解释,还是unsigned short 解释,这两种方式。

同样的我们再看一段代码仔细揣摩揣摩:

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
unsigned short x=65535;
short y=(short)x;
printf("x=%u,y=%d\n",x,y);
return 0;
}

mark

mark

mark

  • 相应位置数值相等,但是表示结果不同,那是因为解释方式不同;unsigned short 和short两种解释 方式。

8.2 不同字长的整数之间的转换

8.2.1 大字长变量向小字长变量强制类型转换

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int x=165537,u=-34991; //int 4B
short y=(short)x,v=(short)u; // short 2B
printf("x=%d,y=%d\n",x,y);
printf("u=%d,v=%d\n",u,v);
return 0;
}

// 当大字长变量向小字长变量强制类型转换时,系统将多余的高位字长部分直接截断舍去,低位直接赋值

结果显示:

mark

mark

小结:

  • 当大字长变量向小字长变量强制类型转换时,系统将多余的高位字长部分直接截断舍去,低位直接赋值

8.2.2 小字长变量向大字长变量强制类型转换

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
short x=-4321; // short 2B
int y=x; // int 4B
unsigned short u=(unsigned short)x; //无符号数的符号拓展直接高位添0即可
unsigned int v=u;
printf("x=%d,y=%d\n",x,y);
printf("u=%u,v=%u\n",u,v);
return 0;
}

结果显示:

mark

  • 这里我们转换成十六进制输出
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
short x=-4321; // short 2B
int y=x; // int 4B
unsigned short u=(unsigned short)x;//无符号数的符号拓展直接高位添0即可
unsigned int v=u;
printf("x=%d,y=%d\n",x,y);
printf("u=%x,v=%u\x",u,v);
return 0;
}

mark

小结

  • 短字长整数到长字长整数的转换,不仅要使相应的位置相等,高位部分还会扩展为原数字的符号位

  • char类型为8位ASCII码整数,转换成int型时,高位部分补0即可。ASCII码由7位二进制数字组成。

  • 无符号数的符号拓展直接高位添0即可

9. 数据的存储和排列(大小端模式)

  • 注意:在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit

    • 现代计算机基本都采用的是字节编址,即每个地址编号中存放1个字节的数据
    • 多字节的数据都存放在连续的字节序列中,根据数据中各字节在连续字节序列中的排位顺序不同,可以分为
      • 大端模式
      • 小端模式
  • 大端模式指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

    • 符合人类阅读的习惯
    • 高地址:存放最低有效字节
    • 低地址:存放最高有效字节
  • 小端模式指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

    • 符合机器的处理方式
mark

9.1 大小端的原因?

  • 在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit
  • 在C语言中除了8bitchar之外,还有16bitshort型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。 因此就导致了大端存储模式和小端存储模式

  • 对于 大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。
  • 小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以随时在程序中(在ARM Cortex 系列使用REV、REV16、REVSH指令 )进行大小端的切换。

9.2 边界对齐的存储方式

  • 假设存储字长32位,可按字节、半字和字寻址。
    • 位、字节、字是计算机数据存储的单位。
    • 位是最小的存储单位,每一个位存储一个1位的二进制码,
    • 一个字节由8位组成。
    • 而字通常为16、32或64个位组成。
    • 一个存储字可以是多个字节组成。
  • 对于32位计算机,数据以边界对齐方式存储,半字地址一定是2的整数倍,字地址一定是4的整数倍,这样无论所取的数据是字节、半字还是字。均可一次访存取出。
    • 所存储的数据不满足上诉要求时,通过填充空白字节使其符合要求。这样虽然浪费了一些存储空间,但是却提高了取指令和取数据的速度
  • 不按边界对齐方式存储的缺点:半字长或字长的指令可能会存储在两个存储字中,此时需要两次访存
    • 并且对高低字节的位置进行调整、连接后才能得到想要的完整指令或数据,从而影响了指令的执行效率和速度。

mark

举个例子:

mark

mark

注意:

  • 边界对齐的存储方式使用了空间换时间的策略
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信