数据的机器级表示与处理

欢迎大家来我的博客做客: re1ife.top

浮点数的表示

  • 浮点数的规格化表示是唯一的:小数点前只有一个非零数

只要对尾数和指数分别编码,就可以表示一个浮点数(实数)。

32位浮点数格式的规格化数表示范围:

+/- 0.1xxxx * 2^E 
  • 第0位:符号位
  • 第1-8位:表示阶码,偏置常数 128,
  • 第9-31位:小数的尾数,规格化尾数的小数点后一位总是1(二进制下),因此可以省去,用23为表示24个数字
    • 最大正数: 0.1111…1 * 211…1 = (1-2-24) * 2 127
      • 0.1 = 2-1
        1. 00000 - 0.000…1 = 0.11…1
      • 偏置常数 + 127 = 255 = 28 - 1
    • 最小正数: 0.100…0 * 200…0 = (2-1 )* 2-128 = 2-129
      • 为什么是0.100…0?
      • 因为小数点之后的1 是被省略的1

综上所述:

机器0: 尾数全部为0的时候,或者落在下溢区域的数。

浮点数比定点数表示的范围更大,但是数的个数没有变多,因此更为稀疏了。

浮点数的规格化

现在所有通用计算机使用 IEEE-754表示浮点数

  • 规格化数的形式: +/- 1.xxxx * R^Exponent

  • 单精度浮点数表示格式:

  • Exponent(阶码 / 指数编码):

    • SP规格化数阶码范围为0000 0001 (-126) ~ 1111 1110 (127)
      • 全1 或 全0 用来表示特殊值
    • bias为 127 (single), 1023 (double)
    • 全部为0时可以看作 -127, 注意此时的偏置常数为127
  • Significand(尾数):

    • 规格化尾数最高位总是1,所以隐含表示,省1位

所以单精度表示的真正的值为: (-1)S x (1 + Significand) x 2Exponent-127

例题:已知单精度浮点数-12.75 ,求在机器中表示的数.

  1. 负数,因此 S为1
  2. 12 = 8 + 4 = 1100B
  3. 0.75 计算方法:
    1. 0.75 * 2 = 1.50 进一
    2. 0.50 * 2 = 1.00 进一
    3. 得到 11B
  4. 12.75 = 1100.11B = 1.10011 x 23
  5. 127 + 3 = 1000 0010 B
  6. 最终结果:
  7. 省去了小数点前面的 1

特殊数字表示

  • 0 的表示
    • -0 : 1 0000 0000 0000 0000 0000 0000 000
    • +0:0 0000 0000 0000 0000 0000 0000 000
  • +/- ♾️
      • 0 11111111 00000000000000000000000
      • :1 11111111 00000000000000000000000
    • 浮点数 / 0 得到的值是♾️ inf
  • 非数: Not a Number(NaN)
    • Exponent 全一, Singnificand非0
    • 例如 sqrt(-4.0)

非规格化数的表示

由上图的上半部分可以看到,2-126 ~ 2-125 中的刻度要比 2-125到2-124更密集。为啥?

[!INFO]
指数位的值加1,相当于将浮点数乘以2,而指数位的值减1,相当于将浮点数除以2。因此,在指
数位相同的情况下,相邻的两个浮点数之间的差值是2的幂次方。

对于2-126到2-125中的每个浮点数,它们的指数位都为-126,因此它们之间的差值是2的-23次幂,即2-126乘以2的-23次幂,约为1.18 x 10-38。而对于2-125到2-124中的每个浮点数,它们的指数位都为-125,因此它们之间的差值是2的-23次幂乘以2,即2-125乘以2的-23次幂,约为2.35 x 10-38。因此,2-126到2-125中的刻度比2-125到2-124更密集。

GAP 区域则是下溢区域, 为了更好利用这部分数字,我门可以使用非规格化表示。

(-1)s * 0.xxxx * 2-128

其他数据的编码

  • 逻辑字符
    逻辑值只有:真或假。

逻辑值的表示用二进制的一位来表示,0 为假 1为真。

  • 西文字符编码

ASCII码来表示。
字符串的操作: 传送比较

  • 汉字及国际字符编码表示

特点: 表意文字、是个方块图形、数量巨大

编码形式:

  1. 输入 ——输入码: 对汉字用相应按键编码
  2. 查找传输处理——内码用于在系统中存储、查找、
  3. 打印显示——字模点阵或轮廓描述

数据宽度与存储

数据的基本宽度

在计算机中最小的处理、存储、传输信息的最小单位是——比特(bit)
二进制信息最基本的计量单位是“字节”(Byte)

  • 现代计算机存储器按字节编址
  • 字节是最小可寻址单位
  • LSB: 小端序 MSB:大端序

在实际使用中,我们也用字来作为单位:
IA-32 中 字: 16位 、 字长:32位

字是一中体系结构如 IA-32规定长度, 字长是数据通路的宽度 ,也就是寄存器、数据传输…的宽度。

  • 数据通路:CPU内部数据流经的路径以及路径上的部件,主要是CPU内部进行数据运算、存储和传送的部件,这些部件的宽度基本上要一致,才能相互匹配
  • 字:表示被处理信息的单位,用来度量数据类型的宽度。

[!INFO] x86体系结构定义“字”的宽度为16位,但从386开始字长就是32位

数据量的度量单位

储存二进制信息是度量单位要远大于字节或字。

  • KB 千字节 1KB = 210字节 = 1024B
  • MB 兆字节 1MB = 220字节 = 1024KB
  • GB 千兆字节 1GB = 230字节 = 1024MB

速率单位:

注意是bit, 我们常说的MB/s,是千字节每秒

  • “千比特/秒”(kb/s),1kbps=103 b/s=1000 bps
  • “兆比特/秒”(Mb/s),1Mbps=106 b/s =1000 kbps
  • “千兆比特/秒”(Gb/s),1Gbps=109 b/s =1000 Mbps
  • “兆兆比特/秒”(Tb/s),1Tbps=1012 b/s =1000 Gbps

程序中的数据类型宽度

  • C语言中的数值类型宽度
    image.png

Compaq Alpha 是一个针对高端应用的64位机器,字长64

数据的存储和排列方式

在计算机中,一个基本数据可能占用多个存储单元。如 int x = 10 x的存放地址为100, 机器数为FFFFFFF6H 占用四个单元。

那么在计算机中给出存放地址一般是最小地址,即x总共占用 100~103 这个四个单元。那么这四个单元内数据的存放顺序就是我们要说的大端序/小端序

左侧即位小端序,右侧为大端序。
大端序符合人的阅读顺序,小端序则是逆序。

大端序(Big Endian): MSB 所在的地址是数的地址
小端序(Little Endian): LSB 所在的地址是数的地址
image.png

C语言中 union 存放顺序是所有成员是从低地址开始。

#include <stdio.h>
int main(){
	union NUM{
		int a;
		char b;
	} num;
	num.a = 0x12345678;
	if(num.b == 0x12)
		printf("Big Endian \n");
	else
		printf("Little Endian \n");
	printf("num.b = 0x%X\n", num.b);
	return 0;
}
字节交换问题

如果一个是大端序、一个是小端序在交换数值是就会发生一些小问题。

数据的表示和运算

按位运算和逻辑运算

按位运算: 对位串实现“掩码”(mask) 操作或相应的其他处理。(主要用于多媒体数据或状态\控制信息处理)。

  • 主要操作:

    • 按位或:“|”
    • 按位与:“&”
    • 按位取反:“~”
    • 按位异或:“^”
  • 逻辑运算: 用于关系表达式的运算。

    • 或:||
    • 与:&
    • 非:!

与按位运算不同的是,逻辑运算符针对的是一个整体。结果类型是了逻辑值。

移位运算与位截断/扩展运算

移位运算:

移位运算主要用于扩大和缩小数值的2、4、8…倍。

  • 操作:
    • 左移: x<<k;
    • 右移: x>>k;

在进行移位运算是保持着

  • 无符号数:高(低)位移出,低(高)位补0
  • 有符号数:
    • 左移:高(低)位移出,低(高)位补0
    • 右移:低位移出,高位补符,可能发生有效数据丢失
      的规则, 也就是说有可能会导致溢出数据丢失

位截断/扩展运算
C语言中没有专门操作运算符,但是在类型转换是会实际发生。

例如在这一段代码中:

int i = 32768;
short si = (short) i;
int j = si;

i 的所占单元要大于short类型,因此在转化为 short时,si等于的是i被截断的值:-32768。
j 作为 int 类型,从short转化为 int 会发生扩展运算,但是值实际没有发生变化, j = -32768

变化过程:

  1. i = 32768 00 00 80 00
  2. si = -32768 80 00
  3. j = -32768 FF FF 80 00

因此从上面我们可以发现截断与扩展的规则:

  • 截断: 强行将高位丢弃,故可能发生“溢出”
  • 扩展:
    • 无符号数:0扩展,前面补0
    • 带符号整数:符号扩展,前面补符

算术运算

讲完了基本的运算规则,那么计算机如何是实现高级语言中的运算呢?

在前面中,我们学习了补码。
那么根据补码的定义:

[ X ] 补 = 2 n + X ( − 2 n − 1 ≤ X ≤ 2 n − 1 , m o d 2 n ) {[X]}_补 = 2^n + X (-{2}^{n-1} \leq X \le 2^{n-1}, mod 2^n) [X]=2n+X(2n1X2n1,mod2n)

我们可以发现有如下公式:

[!INFO]
[x+y]=2n+x+y= 2n+x+2n+y= [x]+[y] (mod 2n)
[x-y]=2n+x-y= 2n+x+2n-y= [x]+[-y] (mod 2n)
[-x] = [x] ‾ \overline{\text{[x]}} [x] + 1

上面的第三个式子就是实现减法的主要工作

利用带标志加法器,可构造整数加/减运算器,进行以下运算:

  • 无符号整数加、无符号整数减
  • 带符号整数加、带符号整数减

这也是为什么我们说,减法是由加法来实现的。

一位加法器——全加器

image.png

如图一位加法器又叫全加器。符号为FA (Fulling Adder)
两个加数为A与B, 低位进位为 Cin ,和为 F ,向高位进位Cout.

运算逻辑表达式:
F = A ⊕ B ⊕ C i n {F = A \oplus B \oplus Cin } F=ABCin
C o u t = A ∧ B + A ∧ C i n + B ∧ C i n {Cout = A \land B + A \land Cin + B \land Cin } Cout=AB+ACin+BCin

image.png

n位加法器

n位全加器可以通过使用 n 个全加器实现。

让我们来欣赏一下加法器的美图吧,家人们:

image.png

我们将 Cout 即全加器的高位进位进行串联,作为下一个全加器的 Cin
是一个逻辑门电路。

如下图连接方式:
image.png

其他所有算术运算部件基于加法器。

图上的基本加法器无法用于两个 n 位带整数(补码) 加减。因为无法判断是否溢出。

  • N位带标志加法器

在编程之中,我们需要经常比较大小, 原理是我们通过底层的加法器做减法得到的标志信息判断的。

下面就是N位带标志加法器的果图,十分的SEXY:

image.png

image.png

  • 标志含义:
    • OF:溢出标志
      • O F = C m ⊕ C m − 1 {OF = C_m \oplus C_{m-1}} OF=CmCm1
    • SF: 符号标志
      • S F = F n − 1 {SF = F_{n-1}} SF=Fn1
    • ZF: 0 标志
      • ZF = 1 当且仅当 F = 0即结果为0 时
    • CF:进位\借位标志
      • C F = C o u t ⊕ C i n {CF = Cout \oplus Cin} CF=CoutCin

那么我们学习了n位带标志的加法器,我们是如何计算的呢?

在前文中,提到了补码的规则。负数的补码是其正数补码尾数+1的值。
因此在计算 A - B 时,实际上计算的是:
[A-B]=2n+A-B= 2n+A+2n-B= [A]+[-B] (mod 2n)

image.png

通过多路选择器(Mux),来选择B输出的是原码还是补码。
当 Sub 为-1,就是做减法,因此我们要使用补码。反之亦然。

那么我们在上面的基础上添加寄存器、移位器以及逻辑控制就可以实现ALU、乘/除以及浮点运算电路

  • ALU
    image.png

可以进行基本的算术运算与逻辑运算,核心电路是带标志位加法器

  • 如何判断结果输出的是针对的哪一种运算?
  • 是看标志ALUop,它的位数决定操作种类。当她有三位时,就有 8 种操作。

整数的加减运算

在计算机中,指针、地址等通常为无符号整数,因此在进行相关操作是要进行无符号整数加减。

无符号整数与带符号整数加减运算电路完全一样, 称之为整数加减运算部件。基于带标志加法器

[!INFO]

  1. 计算机所有运算基于加法器
  2. 加法器不知道运算是带符号数还是不带符号数
  3. 加法器不判断对错,总是取低n为作为结果,生成标志信息
  • 条件标志位:在运算电路中产生,被记录到专门的寄存器。
    • 被称为 程序/状态字寄存器标志寄存器
      image.png

在做加法时判断是否溢出:

  • 无符号整数: CF = 1
  • 带符号整数: OF = 1

在做加法时判断是否溢出:

  • 无符号整数: CF = 1
  • 带符号整数: OF = 1

利用减法判断两数大小:

  • 无符号整数: CF = 0 (减法借位标识),则 A大于B
  • 有符号整数 OF = SF (溢出标志 = 符号标志)的时候,A大于B
减法

n是代表是几位数字。

无符号减公式:(小于零是要模,因为溢出了)

r e s u l t = { x − y ( x − y > 0 ) x − y + 2 n ( x − y < 0 ) result = \left\{ \begin{aligned} x - y &&(x-y>0) \\ x - y + 2^n && (x - y < 0) \end{aligned} \right. result={xyxy+2n(xy>0)(xy<0)

带符号减公式:

r e s u l t = { x − y − 2 n ( 2 n − 1 ≤ x − y ) 正溢出 x − y ( − 2 n − 1 ≤ x − y < 2 n − 1 ) 正常 x − y + 2 n ( x − y < − 2 n − 1 ) 负溢出 result = \left\{ \begin{aligned} x - y - 2^n &&(2^{n-1} \le x-y) && 正溢出\\ x - y &&(-2^{n-1} \le x-y \lt 2^{n-1}) && 正常\\ x - y + 2^n && (x - y < -2^{n-1}) && 负溢出 \end{aligned} \right. result= xy2nxyxy+2n(2n1xy)(2n1xy<2n1)(xy<2n1)正溢出正常负溢出

加法

无符号加公式

r e s u l t = { x + y ( x + y ≤ 2 n ) x − y + 2 n ( 2 n ≤ x + y < 2 n + 1 ) result = \left\{ \begin{aligned} x + y &&(x+y \le 2^n) \\ x - y + 2^n && (2^{n} \le x + y \lt 2^{n+1}) \end{aligned} \right. result={x+yxy+2n(x+y2n)(2nx+y<2n+1)

带符号减公式:

r e s u l t = { x + y − 2 n ( 2 n − 1 ≤ x + y ) 正溢出 x + y ( − 2 n − 1 ≤ x + y < 2 n − 1 ) 正常 x + y + 2 n ( x + y < − 2 n − 1 ) 负溢出 result = \left\{ \begin{aligned} x + y - 2^n &&(2^{n-1} \le x+y) && 正溢出\\ x + y &&(-2^{n-1} \le x+y \lt 2^{n-1}) && 正常\\ x + y + 2^n && (x + y < -2^{n-1}) && 负溢出 \end{aligned} \right. result= x+y2nx+yx+y+2n(2n1x+y)(2n1x+y<2n1)(x+y<2n1)正溢出正常负溢出

整数乘除法

整数乘法

高级语言中, 两个n位数乘积的会得到 2n位 的数字,但是我们只取低n位。

在计算机内部,x2带符号整数不一定是正数(溢出成负值),但是浮点数一定是正数。

例如,四位的带符号整数,x = 5,会发生溢出。 计算过程: image.png

由于是两个4位带符号整数相乘会得到一个8位的整数,我们取低四位即 1001 他的真值就是 -111 ,因此就是 -7。

image.png

假定两个n位无符号整数xuyu对应的机器数为XuYupu=xu×yupu为n位无符号整数且对应的机器数为Pu; 两个n位带符号整数xsys对应的机器数为XsYsps=xs×ysps为n位带符号整数且对应的机器数为Ps。 若Xu=XsYu=Ys,则Pu=Ps

-溢出判断: - 无符号:若Puh=0,则不溢出 - 带符号:若Psh每位都等于Ps的最高位,则不溢出

通过X*Y的高n位可以用来判断溢出 无符号:若高n位全0,则不溢出,否则溢出 带符号:若高n位全0或全1且等于低n位的最高位,则不溢出。

image.png

整数除法

手算二进制除法:
image.png

其实与一般除法无异。

在带符号的整数,n 位整数除n位整数,除 -2n-1 / - 1 = 2n-1 发生溢出。

商的绝对值不可能比被除数的绝对值更大。

整数除法其商也是整数,随意在无法整除是要舍入。
通常下,是按照0方向舍入。
整数除0的结果无法用机器数表示, 会出现异常。

我们举个例子:

int a = 0x80000000
int b = a / -1
printf("%d\n",b)

运行结果是: -2147483648。
通过 Objdump反汇编可以得知, -1 被优化为了取负指令neg ,因此没有发生除法溢出。
但是,结果是错误的,0x80000000 是 -231 , 结果应该是 231 . 但是由于32位的补码无法表示,因此溢出。

int a = 0x80000000;
int b = -1;
int c = a / b;
printf("%d\n",c)

结果是 Float Point Exception. 其实就是编译器底层无法优化为取反指令。

变量与常数之间的运算

计算机中除法运算更为复杂,因此不能使用流水线方式,一次除法需要消耗30多个或者更多始终周期。

为了缩短时间,编译器在处理一个变量与一个2的幂次形式的整除时,经常才去右移运算实现。

在可以整除的时候可以直接右移。

  • 无符号:逻辑右移
    • 12/4 = 3 = 0000 1100 >> 2 = 0000 0011
  • 有符号:算术右移
    • -12/4 = 3 = 1111 0100 >> 2 = 1111 1101

不能整除时,右移移出的位中有非0,需要处理。

  • 不能整除时,采用朝0舍入,即截断处理。
    • 无符号数、带符号正整数:移出低位之后直接丢弃。
      • 举例:14 /4 = 3 = 0000 1110 >> 2 = 0000 0011
    • 带符号负整数:加偏移量(2k - 1) 然后右移k位,低位截断。k是右移位数。
    • ❎直接截断: -14 / 4 = -4 = 1111 0010 >> 2 = 1111 1100 = -4
    • 纠偏右移: k = 2 (-14 + 22 - 1) / 4= -3 = 1111 0101 >> 2 = 1111 11101 = -3

浮点数运算及结果

<<<<<<< HEAD
image.png

可能会有几种情况:

  • 阶码上溢:一个正指数超过最大允许值 => +∞/-∞ /溢出
  • 阶码下溢:一个负指数超过最小允许值 => +0/-0
  • 尾数溢出: 最高有效位有进位 => 右规
    • 1.5 + 1.5 = 11.00…000 但是可以右移将非规格化数合规
  • 非规格化位数:数值部分高位为0 => 左规
    • 1.5 - 1.0 = 0.10…0 尾数要左移,阶码减1
  • 右规或对阶
IEEE规定的五种异常情况
  1. 无效运算
    1. 运算时有一个是非有限数 (♾️)
    2. 结果无效: NaN 。。。。
  2. 除以0 (无限大)
  3. 数太大(阶上溢)
  4. 数太小(阶下溢)
  5. 结果不精确如 1 / 3 无法精确表示

浮点数目的运算过程可以大致分为一下几个过程:

1.求阶差: ∆e=Ye – Xe (若Ye > Xe,则结果的阶码为Ye)

2. 对阶: 将两个小数的阶码对齐,小阶向大阶对齐

  • 举个例子: 1.123 x 105 + 2.230 x 103 = 1.123 x 105 + 0.02230 x 105

IEEE 754尾数右移时,要将隐含的“1”移到小数部分,高位补0,移出的低位保留到特定的“附加位”上

方便我们来计算。

3. 尾数加减: Xm*2Xe-Ye ± Ym

4. 规格化:

  • 当尾数高位为0,则需左规:尾数左移一次,阶码减1,直到MSB为1或阶码为00000000(-126,非规格化数)
  • 每次阶码减1后要判断阶码是否下溢(比最小可表示的阶码还要小)
  •   当尾数最高位有进位,需右规:尾数右移一次,阶码加1,直到MSB为1
    
  • 每次阶码加1后要判断阶码是否上溢(比最大可表示的阶码还要大)

阶码溢出异常处理:阶码上溢,则结果溢出;阶码下溢到无法用非规格化数表示,则结果为0

5. 若尾数比规定位数长(有附加位),则考虑舍入
6. 若尾数结果为0, 结果也应该为0

附加位

为了保证每次运算尽可能不丢失精度,我们所想出来的办法是——附加位。

IEEE 754: 规定中间结果必须在右边加两个附加位。

  1. Guard 保护位:在Significand右边的位
  2. Round 舍入位:在保护位右边的位

作用:用以保护对阶时右移的位或运算的中间结果。
处理: ①左规时被移到significand中; ② 作为舍入的依据。

举例子:image.png
默认舍入方式:就近舍入。

image.png

Z1和Z2分别是结果Z的最近的可表示的左、右两个数 )

  1. 就近舍入(精度最高):舍入为最近可表示的数
    非中间值:0舍1入;
    中间值:强迫结果为偶数-慢

举例:
附加位为
01:舍
11:入
10:(强迫结果为偶数)
2. 朝+∞方向舍入:舍入为Z2(正向舍入)
3. 朝-∞方向舍入:舍入为Z1(负向舍入)
4. 朝0方向舍入:截去。正数:取Z1; 负数:取Z2

image.png

  • C语言中float和double分别对应IEEE 754单精度浮点数格式和双精度浮点数
  • long double类型的长度和格式随编译器和处理器类型的不同而有所不同,IA-32中是80位扩展精度格式
  • 从int转换为float时,不会发生溢出,但可能有数据被舍入
  • 从int或 float转换为double时,因为double的有效位数更多,故能保留精确值
  • double转换为float和int时,可能发生溢出,此外,由于有效位数变少,故可能被舍入
  • float 或double转换为int时,因为int没有小数部分,所以数据可能会向0方向被截断

范围与精度

float型表示范围:
最大数据: +1.11…1 x 2127 约为 +3.4x1038

大数吃小数

这个是精度问题所导致的:

image.png

在程序之中,我们总是先计算括号部分,因此在第二行的中,1.0在对阶中就被省略成0了。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值