计算机组成原理(二)数据的表示和运算


一个展示各种类型的机器数表示的在线网站:http://www.binaryconvert.com/index.html

定点数

定点数的表示

定点小数本质上和定点整数没有区别,这里只讨论定点整数

基本概念

在计算机中参与运算的机器数有两大类:

  • 无符号数:没有符号位,机器数就是真值的二进制表示

  • 有符号数:最高位为符号位,0表示正数,1表示负数【移码特殊,1正0负】

原码、反码、补码、移码是计算机对于定点数的几种表示方法

  • 原码是真值直观的机器数表示,用机器数的最高位表示该数的符号,其余的各位表示数的绝对值,零表示不唯一。

  • 补码是原码全部取反后加一,将减法运算转换成加法运算,零表示唯一。

    原码表示法的加减法操作比较复杂,对于两个不同符号数的加法(或同符号数的减法),先要比较两个数的绝对值大小,然后用绝对值大的数减去绝对值小的数,最后还要给结果选择合适的符号。

  • 反码是原码全部取反,零表示不唯一

  • 移码是用真值 X \rm X X 加上一个常数,这个数通常取 2 n 2^n 2n,通常用来表示浮点数的阶码,只能用于表示整数,零表示唯一

详细见下方 “一点探究”

定点数的运算

移位运算

  • 算数移位(针对有符号数)

    • 原码:高低位补 0

    • 反码:高低位补符

    • 补码:高位补符,低位补 0【可见补码右侧(从右往左数第一个1右边)与原码性质相同,左侧与反码性质相同】

  • 逻辑移位(针对无符号数)

    • 全补0
  • 循环移位

    在这里插入图片描述

符号扩展(Type Cast 类型转换)

假设从A类型 -> B类型,如果B类型包含A类型能表示的所有数,简单说就是从“小”转“大”,就可以进行无损(无精度丢失)转换

char -> int -> long -> double,float -> double

虽然书上是说从 float 到 double 转换过程中没有损失/误差,但如果一个小数是没办法有二进制表示出来的时候,这个转化不算是损失,但也是有误差的,不过做题的话还当做没有损失吧

int main() {
	float f = 1.25; double d = 1.25;
	cout << (d == (double)f) << endl; 
    float f1 = 1.2;
	double d1 = 1.2, d2 = f1;
	cout << (d1 == (double)f1) << endl; 
    cout << (d1 == d2) << endl;
	cout << d1 << endl; 
    cout << d2 << endl;
	cout << (f1 == (float)(double)f1) << endl; 
    return 0;
}

在这里插入图片描述

溢出判断

定点数溢出

  • 上溢:大于机器所能表示的最大正数

  • 下溢:小于机器所能表示的最小负数

浮点数溢出

  • 上溢会引发中断
  • 下溢做机器零处理

关于浮点数溢出的定义网上群魔乱舞,这里我相信王道资料写的

在这里插入图片描述

溢出判断方法

  • 参加操作的两个数符号相同,结果与操作数符号不同,即为溢出

  • 最高位与次高位进位相同则无溢出,否则溢出

  • 双符号位时(变形补码,模四补码),相同无溢出;不同则溢出。此时最高位符号代表真正符号

    • 01表示正溢出:两个正数相加导致的溢出

    • 10表示负溢出:两个负数相加导致的溢出

其它

  • 发生进位/借位,标志位 CF = 1

  • 发生溢出 标志位 OF = 1

  • 无符号数一般不讨论溢出的问题,只有进位/借位问题

  • 无符号数需要进位/借位的简单计算方法 P56

加减乘除运算

数据的存储和排列

小端存放

将最低有效字节存储在地址编号最小的位置,就是我们感觉上的“倒着存“

比如一个 int 类型的变量机器数为 01234567Hi 的地址为 00H,则对应关系如下

00H01H02H03H
67H45H23H01H

“倒过来存”是针对最小数据单元,int 整数是符合小端存储的,string 字符串并不符合,因为字符串一个最小数据单元就一个字节,那还往哪儿倒呢(汉字另说)

边界对齐

在这里插入图片描述

数据不按边界对齐方式存储时,可以充分利用存储空间,但半字长或字长的指令可能会存储在两个存储字中,此时需要两次访存,并且对高低字节的位置进行调整、连接之后才能得到所要的指令或数据,影响了指令的执行效率。

浮点数

浮点数的表示

普通浮点数格式

N = r E × M N=r^E\times M N=rE×M

  • 基数 r r r

    • 基数变大,表示范围越大,发生因为对阶或者尾数溢出或者规格化的次数显著减少,运算速度可以提高,但精度变低
  • 尾数 M M M

  • 阶码 E E E

IEEE754格式(以单精度为例)

( − 1 ) S × 1. M × E − 127 (-1)^{S} \times 1 . M \times^{E-127} (1)S×1.M×E127

在这里插入图片描述

符号位 S:1位

尾数M:23位(隐藏一位),原码表示

阶码 E:8位,移码表示,偏移值为127(阶码的范围是1~254),一些规定:

  • 阶码全0,尾数全0,表示 0

  • 阶码全1,尾数全0,表示无穷大(符号位为 0 表示正无穷,为 1 表示负无穷)

  • 阶码全0,尾数不为0,表示非规格化数

  • 阶码全1,尾数不为0,表示NaN(非数值)

规格化浮点数

  • 尾数最高位必须是一个有效值

  • 左规与右规

  • 原码规格化

    • 尾数最高位一定是1(IEEE754浮点数尾数隐藏了一位整数1,所以看起来好像IEEE754不满足规格化)
  • 补码规格化

    • 尾数最高位一定和符号位相反

浮点数的运算

对阶

先求阶差,然后小阶向大阶看齐

尾数求和

尾数相加

规格化

  • 左规
  • 右规

要分清楚“对阶”和“右规”,虽然“对阶”也是右移、阶码+1,也要考虑舍入。但它们是两种操作,对阶的右移往往会进行多次,而右规的右移一般只要一次(除非采用“0舍1入”导致再次溢出)

舍入

右规和对阶之后可能需要舍入

  • 0舍1入法

    • 右规可能需要多次
  • 恒置1法

溢出判断

浮点运算中,运算结果超出尾数表示范围却不一定溢出,只有规格化后阶码超出所能表示的范围时,才发生溢出

  • 阶码上溢:进入中断处理

  • 阶码下溢:按机器0处理

  • 溢出

    整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:

    double d1 = 0.0 / 0; // NaN Not a Number
    double d2 = 1.0 / 0; // Infinity 无穷大
    double d3 = -1.0 / 0; // -Infinity 负无穷大
    

一点问题

对于位数相同的定点数和浮点数,可表示的浮点数个数比定点数个数多吗?

一样多,可表示的数据个数取决于编码所采用的位数。编码位数一定,编码出来的数据个数就是一定的。n位编码只能表示2n个数,所以对于相同位数的定点数和浮点数来说,可表示的数据个数应该一样多(有时可能由于一个值有两个或多个编码对应,编码个数会有少量差异)。

一点探究

关于原码、补码、反码、移码

最开始学计组的时候,总以为是 原码->反码->补码 这个过程,今天看了唐朔飞的《计算机组成原理》,又查了英文才发现不该那么理解

原码True Form
补码Complement
反码radix-minus-one complement
base minus one’s complement

反码的英文直译过来就是 “一个数的补码形式剪去1”,也就是说这个过程应该是 原码->补码->反码

Q1:补码是“补”的什么?

例如,时钟指示6点,想让它指示3点,那么我们可以顺时针转9圈,也可是逆时针转3圈,转换成数学语言就是
− 3 ≡ + 9   ( m o d   12 ) -3\equiv +9\ (mod\ 12) 3+9 (mod 12)
对于 m o d    12 \mod 12 mod12 而言, − 3 -3 3 9 9 9 互为补数。

这个思想转换到定点数的表示里,就是一个 n n n 为负数可以用它的正补数来代替

如下:

#include <iostream>
using namespace std;

int main(){
	short a = -20;
	unsigned short b = a;
	cout << a + 65536 << endl;//两者是相同的
	cout << b << endl;
}

自己的理解,所谓的有符号数,其实是用 [ 0 , 2 n − 1 − 1 ] [0,2^{n-1}-1] [0,2n11] 来表示正数,用 [ 2 n − 1 , 2 n − 1 ] [2^{n-1},2^{n}-1] [2n1,2n1] 来表示负数,两个区间都是 2 n − 1 2^{n-1} 2n1 个数

根据教材补码的定义,换种说法:

对于任何 n 位有符号数 a a a ,如果 a a a 是负数, [ 2 n − 1 , 2 n − 1 ] [2^{n-1},2^n-1] [2n1,2n1] 内一定存在一个数 b b b,使得
b = a + 2 n b=a+ 2^n b=a+2n

此时 b b b 的原码就是 a a a 的补码形式。

Q2:为什么负数表示的范围比正数 “多一个” ?

还有一个问题,之前也会不理解,为什么负数表示的范围比正数 “多一个” 呢?

例如,int 类型最大值 2147483647 2147483647 2147483647,最小值 − 2147483648 -2147483648 2147483648

**首先肯定不是多一个,正数区间包含 0 0 0,两个区间数的个数是一样多的。**之前看过一些解释,大概的意思如下

因为补码形式中 + 0 +0 +0 − 0 -0 0 是相同的,为了不浪费,让 − 0 -0 0 去表示 − 2 n -2^n 2n

其实也可以这么来看,比如八位有符号数的范围是 [ − 128 , 127 ] [-128,127] [128,127] ,将 a = − 128 a=-128 a=128 代入上面的式子 b = a + 2 n b=a+ 2^n b=a+2n b b b 就是 2 7 2^7 27,那么就理所应当用 1000   0000 B 1000\ 0000B 1000 0000B ,也就是 − 0 -0 0 去表示 − 2 n -2^n 2n

Q3:无符号数与有符号数如何运算?

无符号数与有符号数的运算,会把有符号数视为无符号数,然后进行运算

无符号数与正数运算,与无符号数和无符号数运算无异,除了正数的范围小了一点。

无符号数与负数运算,根据这个定义

对于任何 n 位有符号数 a a a ,如果 a a a 是负数, [ 2 n − 1 , 2 n − 1 ] [2^{n-1},2^n-1] [2n1,2n1] 内一定存在一个数 b b b,使得
b = a + 2 n b=a+ 2^n b=a+2n

此时 b b b 的原码就是 a a a 的补码形式。

假设有一个无符号数为 A A A 和一个负数 a a a,现在做运算 A + a A+a A+a 运算,就等同于 A A A a a a 的补数 b b b 相加, m o d    2 n \mod 2^n mod2n 是因为结果可能大于 2 n 2^n 2n (无符号数对于超过最大值的处理方式是直接舍去高位,即运算结果 m o d    2 n \mod 2^n mod2n
A + a ≡ ( A + a + 2 n ) m o d    2 n ≡ ( A + b ) m o d    2 n A+a\equiv(A+a+2^n)\mod 2^n \equiv(A+b)\mod 2^n A+a(A+a+2n)mod2n(A+b)mod2n

Tips1:相反数间补码关系

int x = 10;
int y = ~x + 1;//此时y是-10

对于任何一个数 x x x ,将 x x x 的补码连带符号位取反后 + 1 + 1 +1,是 − x -x x 的补码。

这个对 0 0 0 也是成立的

Tips2:移补码转换技巧

书上定义对比(当移码常数取 2 n 2^n 2n 时)
[ x ] 补 = x   ( x ≥ 0 ) [ x ] 补 = 2 n + 1 + x   ( − 2 n ≤ x < 0 ) [ x ] 移 = 2 n + x   ( − 2 n ≤ x < 2 n ) [x]_补= x\ (x \ge 0)\\ [x]_补= 2^{n+1}+x\ (-2^n\le x < 0)\\ [x]_移 =2^n+x\ (-2^n \le x< 2^n ) [x]=x (x0)[x]=2n+1+x (2nx<0)[x]=2n+x (2nx<2n)
联立有
[ x ] 补 + 2 n = [ x ] 移   ( x ≥ 0 ) [ x ] 补 = 2 n + [ x ] 移   ( − 2 n ≤ x < 0 ) [x]_补+2^n= [x]_移\ (x \ge 0)\\ [x]_补= 2^{n}+[x]_移\ (-2^n\le x < 0)\\ [x]+2n=[x] (x0)[x]=2n+[x] (2nx<0)
所以移码和补码的符号只差一个符号位。

Tips3:补原码转换技巧

原码到补码(补码到原码做法一模一样)

从右边往左边看,遇见第一个1前照抄,然后后面的除了符号位全部取反

比如:

原码:10001110

补码:11110010

Tips4:补码1的个数与大小关系

根据在Q1中的定义, b = a + 2 n b=a+ 2^n b=a+2n ,对于越小的负数 a a a ,得到的 b b b 也就越小, b b b 1 1 1 就越少

补码正数和负数在“1的个数”与“大小关系”上是一样的

正数:0000 (0)最小,0111 (15)最大

负数:1000 (-16)最小,1111 (-1)最大

  • 模四补码(变形补码,双符号位)

    • 更容易检测加减运算中的溢出问题
    • 存储模四补码中需要一个符号位,只有在把两个模四补码的数送至ALU中完成加减、移位运算时,才需要双符号位,即只在ALU中才有双符号位这一说
    • 高符号位代表真正的符号,低符号位参与运算来判断是否发生溢出
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值