《深入理解计算机系统》(CSAPP)第二章——信息的表示和处理 知识点总结
CASPP
- 第二章 信息的表示与处理
- 2.1 信息存储
- 2.1.1 字数据大小
- 2.1.2 寻址和字节顺序
- 2.1.3 布尔运算
- 2.1.4 位移运算
- 2.2 整数表示
- 2.2.1 整数类型数据
- 2.2.2 无符号数编码
- 2.2.3 补码编码
- 2.2.4 有符号数和无符号数的转换
- 2.2.5 考虑各种越界情况
- 2.2.6 扩展与截断规则
- 2.2.7 经典案例
- 2.3 整数运算
- 2.3.1 无符号加法
- 2.3.2 补码加法
- 2.3.3 无符号乘法
- 2.3.4 补码乘法
- 2.3.5 乘以常数
- 2.3.6 除以2的幂
- 2.4 浮点数
- 2.4.1 二进制小数
- 2.4.2 IEEE浮点表示
- 2.4.3 舍入
- 2.4.4 浮点运算
- 2.4.5 C中的浮点数
第二章 信息的表示与处理
2.1 信息存储
基础知识:对大部分机器来说,字节(byte)是8位的块,即8bit,每个bit存放一个0或1。内存中每个字节都有一个唯一的数字来表示,也被称为地址。所有地址的集合被称作虚拟内存空间,它是真实物理存储空间的映射。C语言的指针的值就是这些地址,通常以十六进制的数字表示,这样就可以通过指针值的类型生成不同的机器级代码,直接访问对应地址指向位置的值。整个计算机为程序提供了一个看上去统一的字节数组。
2.1.1 字数据大小
字(word),字长(word size),指明指针数据的标称大小,且虚拟地址是以这样的一个字来编码的,也就是用一个字长的二进制码记录的数字。对于一个字长为www的机器,它的虚拟地址范围为000~2w−12^w-12w−1,也就是最多只能访问2w2^w2w个字节。目前常见的机器有32位和64位两种,这里位指的就是字长。注意32位程序和64位程序的差别在于编译器编译的方式不同,而不是运行机器的不同。
常见的类型在32/64位程序中包含的字节数如下:
C声明 | 字节数(32位) | 字节数(64位) |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
unsigned | 4 | 4 |
long | 4 | 8 |
unsigned long | 4 | 8 |
float | 4 | 4 |
double | 8 | 8 |
int32_t | 4 | 4 |
int64_t | 8 | 8 |
指针 | 4 | 8 |
C语言一个很大的问题就是没有统一int声明的数据大小,有时被当成4字节,有时被当成8字节,而这可能在移植代码时产生严重错误。后来为了统一,就产生了int32_t和int64_t。C中float是有统一标准的。
2.1.2 寻址和字节顺序
我们已知计算机中处理的最小单位就是字节,8位。任何数据类型都是可以看作字节的数组,比方说int就是4个字节,32位组成的,那么这些字节在计算机中的存储顺序如何呢?根据顺序和逆序分成了大端法和小端法,顺序指高位在前低位在后,逆序则相反。例如0x87 65 43 21,在大端法中存储为87 65 43 21,在小端法中则存储为21 43 65 87,切记不是12 34 56 78。
大端小端表示法分别被谁使用呢?大部分Intel兼容机使用小端表示,而IBM和Oracle使用大端表示。对于操作系统,Windows和Linux为小端表示,Sun则为大端表示。大端小端表示对于程序是没有影响的。
2.1.3 布尔运算
重点就是区分位运算和逻辑运算。
运算 | 位运算 | 逻辑运算 |
---|---|---|
与 | & | && |
或 | | | || |
非 | ~ | ! |
异或 | ^ | 无 |
位运算是对每一位进行操作。逻辑运算中,非0均视为1,运算结果为0和1。
位操作举例:
void inplace_swap(int* x, int* y){*y = *x ^ *y;*x = *x ^ *y;*y = *x ^ *y;
}
步骤 | *x | *y |
---|---|---|
0 | *x | *y |
1 | *x | *x^*y |
2 | *x^(*x^*y)=(*x^*x)^*y=*y | *x^*y |
3 | *y | *x |
2.1.4 位移运算
C语言中位移操作分为左移、逻辑右移和算术右移三种。左移符号为<<,右移符号为>>。左移操作会进行低位补0,逻辑右移会在左边补0,算术右移则会在左边补移位前的最高位值。一个char类型数据,例如1001_1100,向右移3位就是1111_0011。几乎所有编译器都会对有符号数采用算术右移,而不是逻辑右移。无符号数为保证正确性会采用逻辑右移。无符号数相比有符号数将产生很多问题,以致Java中舍弃了unsigned类型数据,只用int。
运算优先级:乘除>加减>移位>位运算
2.2 整数表示
2.2.1 整数类型数据
以64位为例,32位类似
C数据类型 | 最小值 | 最大值 |
---|---|---|
char | -128(−27-2^7−27) | 127(27−12^7-127−1) |
short | -32 768(−215-2^{15}−215) | 32767(215−12^{15}-1215−1) |
int | -2 147 483 648(−231-2^{31}−231) | 2 147 483 647(231−12^{31}-1231−1) |
long | -9 223 372 036 854 775 808(−263-2^{63}−263) | -9 223 372 036 854 775 807(263−12^{63}-1263−1) |
unsigned | 0 | 4 294 967 295(232−12^{32}-1232−1) |
2.2.2 无符号数编码
令x=[xw−1,…,x1,x0]x=[x_{w-1},…,x_{1},x_{0}]x=[xw−1,…,x1,x0]
定义B2Uw(x)=x020+x121+……+xw−12w−1B2U_{w}(x)=x_{0}2^0+x_{1}2^1+……+x_{w-1}2^{w-1}B2Uw(x)=x020+x121+……+xw−12w−1
原理:无符号数编码具有唯一性,函数B2U(x)B2U(x)B2U(x)是一个双射。
利用归纳法即可证明。
UMin=0,UMax=2w−1UMin=0,UMax=2^w-1UMin=0,UMax=2w−1
2.2.3 补码编码
令x=[xw−1,…,x1,x0]x=[x_{w-1},…,x_{1},x_{0}]x=[xw−1,…,x1,x0]
定义B2Tw(x)=−xw−12w−1+xw−22w−2+……+x020B2T_{w}(x)=-{x_{w-1}}2^{w-1}+x_{w-2}2^{w-2}+……+x_{0}2^0B2Tw(x)=−xw−12w−1+xw−22w−2+……+x020
原理:补码编码的唯一性,函数B2Tw(x)B2T_{w}(x)B2Tw(x)是一个双射。
若TMin<=B2Tw(x)<=TMaxTMin<=B2T_{w}(x)<=TMaxTMin<=B2Tw(x)<=TMax,由上式可知,TMin=−2w−1TMin=-2^{w-1}TMin=−2w−1,TMax=2w−1−1TMax=2^{w-1}-1TMax=2w−1−1。
类似,有反码和原码的定义:
反码:B2Ow(x)=−xw−1(2w−1−1)+xw−22w−2+……+x020B2O_{w}(x)=-{x_{w-1}}(2^{w-1}-1)+x_{w-2}2^{w-2}+……+x_{0}2^0B2Ow(x)=−xw−1(2w−1−1)+xw−22w−2+……+x020
原码:B2Sw(x)=(−1)xw−1(xw−22w−2+……+x020)B2S_{w}(x)=(-1)^{x_{w-1}}(x_{w-2}2^{w-2}+……+x_{0}2^0)B2Sw(x)=(−1)xw−1(xw−22w−2+……+x020)
反码=补码+1=[1…11]-原码
2.2.4 有符号数和无符号数的转换
最重要的一点就是转换前后数的位不变,即二进制码相同。
U2Tw(u)=u,u<=TMax;u−2w,u>TMaxU2T_{w}(u)=u,u<=TMax;u-2^w,u>TMaxU2Tw(u)=u,u<=TMax;u−2w,u>TMax
T2Uw(x)=x+2w,x<0;x,x>=0T2U_{w}(x)=x+2^w,x<0;x,x>=0T2Uw(x)=x+2w,x<0;x,x>=0
2.2.5 考虑各种越界情况
C语言中,当一个运算数是有符号,一个运算数是无符号时,有符号数将被强制转换为无符号数在进行计算。下表将详细讨论各种情况。
表达式 | 类型 | 求值 |
---|---|---|
0 == 0 | 无符号 | 1 |
-1 < 0 | 有符号 | 1 |
-1 < 0U | 无符号 | 0* |
2147483647 > -2147483647-1 | 有符号 | 1 |
2147483647U > -2147483647-1 | 无符号 | 0* |
2147483647 > (int)2147483648U | 有符号 | 1* |
-1 > -2 | 有符号 | 1 |
(unsigned)-1 > -2 | 无符号 | 1 |
所以注意这几点:有unsigned出现先将所有值全部转换为unsigned类型再运算、注意强制类型转换、明确有符号无符号数边界值的转换……
2.2.6 扩展与截断规则
当一个数据类型要转换到具有更多位的类型时(例如short到int、16位到32位),应该保证变化前后数值不变,而如何给新增位赋值就是扩展问题要解决的。
1.无符号数采用零扩展
设uw=[uw−1,…,u1,u0]u_{w}=[u_{w-1},…,u{1},u_{0}]uw=[uw−1,…,u1,u0],w′>ww'>ww′>w,扩展后uw′=[uw′−1,…,uw,uw−1,…,u0]u_{w'}=[u_{w'-1},…,u_{w},u_{w-1},…,u_{0}]uw′=[uw′−1,…,uw,uw−1,…,u0],其中uw′−1=uw′−2=…=uw=0u_{w'-1}=u_{w'-2}=…=u_{w}=0uw′−1=uw′−2=…=uw=0。由计算可知B2Uw(uw)=B2Uw′(uw′)B2U_{w}(u_{w})=B2U_{w'}(u_{w'})B2Uw(uw)=B2Uw′(uw′)。
2.补码采用符号扩展
因为扩展的新增位是用符号位来赋值的,所以被称为符号赋值。
设xw=[xw−1,…,x1,x0]x_{w}=[x_{w-1},…,x_{1},x_{0}]xw=[xw−1,…,x1,x0],w′>ww'>ww′>w,扩展后xw′=[xw′−1,…,xw,xw−1,…,x0]x_{w'}=[x_{w'-1},…,x_{w},x_{w-1},…,x_{0}]xw′=[xw′−1,…,xw,xw−1,…,x0],其中xw′−1=xw′−2=…=xw=xw−1x_{w'-1}=x_{w'-2}=…=x_{w}=x_{w-1}xw′−1=xw′−2=…=xw=xw−1。由计算可得B2Tw(xw)=B2Tw′(xw′)B2T_{w}(x_{w})=B2T_{w'}(x_{w'})B2Tw(xw)=B2Tw′(xw′),可能不像无符号位那么明显,请大家自己代公式计算。此外使用归纳法证明也行。
以书上习题2.23(P56)为例
int fun1(unsigned word){return (int)((word<<24)>>24);
}
int fun2(unsigned word){return ((int)word<<24)>>24;
}
w | fun1(w) | fun2(w) |
---|---|---|
0x00 00 00 76 | 0x00 00 00 76 | 0x00 00 00 76 |
0x87 65 43 21 | 0x00 00 00 21 | 0x00 00 00 21 |
0x00 00 00 C9 | 0x00 00 00 C9 | 0xFF FF FF C9 |
0xED CB A9 87 | 0x00 00 00 87 | 0xFF FF FF 87 |
fun1是无符号数的移位,fun2则是有符号数的移位,注意类型转换优先级比运算符高,比括号低。
3.无符号数截断
令uw=[uw−1,…,u1,u0]u_{w}=[u_{w-1},…,u_{1},u_{0}]uw=[uw−1,…,u1,u0],k<wk<wk<w,截断后uk=[uk−1,…,u0]u_{k}=[u_{k-1},…,u_{0}]uk=[uk−1,…,u0]。因为删除的位值都是2k2^k2k的倍数,它们模2k2^k2k为0,所以B2Uw(uw)mod2k=B2Uk(uk)B2U_{w}(u_{w})\mod2^k=B2U_{k}(u_{k})B2Uw(uw)mod2k=B2Uk(uk)。
4.截断补码数值
和无符号数的操作是相同的,都是将多余的部分舍去,xw=[xw−1,…,x1,x0]x_{w}=[x_{w-1},…,x_{1},x_{0}]xw=[xw−1,…,x1,x0],k<wk<wk<w,截断后xk=[xk−1,…,x0]x_{k}=[x_{k-1},…,x_{0}]xk=[xk−1,…,x0]。令x′=B2Tk(xk),x=B2Tw(xw)x'=B2T_{k}(x_{k}),x=B2T_{w}(x_{w})x′=B2Tk(xk),x=B2Tw(xw),因为截断后有无符号数的二进制码是相同的,所以x′=U2Tk(xmod2k)x'=U2T_{k}(x\mod2^k)x′=U2Tk(xmod2k)
2.2.7 经典案例
Ex1:
float sum_elements(float a[], unsigned length){int i;float result=0;for(itn i=0; i<length; i++)result+=a[i];return result;
}
经典输入length=-1,程序直接报错。因为length为unsigned,-1将会得到UMax>0进入循环,但此时数组为空,于是出错。所以尽量不要用unsigned写程序,尽管它表面上看起来更合理。
Ex2:
int strlonger(char* s, char* t){return strlen(s)-strlen(t)>0;
}
//注意strlen的声明
size_t strlen(const char* s);
由于头文件stdio.h中将size_t定义成了unsigned int,这将导致strlen(s)-strlen(t)始终大于0,除非它们相等。为避免unsigned出错,可以写成比较大小的形式,strlen(s)>strlen(t)。
2.3 整数运算
2.3.1 无符号加法
假设无符号数的位数为w,那么它的取值范围就是[0,2w−1][0,2^w-1][0,2w−1]。∀x,y∈U,x+y∈[0,2w+1−2]\forall x,y\in U,x+y \in [0,2^{w+1}-2]∀x,y∈U,x+y∈[0,2w+1−2],这时可能会产生溢出。处理方式如下:
x+wuy={x+yif x+y<2wx+y−2wif 2w≤x+y<2w+1x+^{u}_{w}y=\begin{cases} x+y &\text{if } x+y<2^{w} \\ x+y-2^{w} &\text{if } 2^{w} \le x+y< 2^{w+1} \end{cases}x+wuy={x+yx+y−2wif x+y<2wif 2w≤x+y<2w+1
溢出就减去2w2^w2w即可,想想也比较合理,就是去掉结果溢出的最高位1得到的值。
那么如何检测程序中是否发生溢出呢?
不妨令s=x+wuys=x+_w^uys=x+wuy,x+y溢出当且仅当s<ys<ys<y或s<xs<xs<x。
无符号加法运算属于阿贝尔群(Abelian group),有单位元0,可交换,可结合,还有逆元。求无符号数的逆元:
−wux={xif x=02w−xif x>0-_w^ux=\begin{cases} x &\text{if }x=0 \\ 2^w-x &\text{if }x>0 \end{cases}−wux={x2w−xif x=0if x>0
2.3.2 补码加法
∀x,y∈[−2w−1,2w−1−1],x+y∈[−2w,2w−2]\forall x,y\in [-2^{w-1},2^{w-1}-1],x+y\in[-2^{w},2^{w}-2]∀x,y∈[−2w−1,2w−1−1],x+y∈[−2w,2w−2],也会产生溢出,但比无符号数复杂,因为分为上溢和下溢两种不同的情况。处理方式如下:
x+wty={x+y−2wif 2w−1≤x+yx+yif −2w−1≤x+y<2w−1x+y+2wif x+y<−2w−1x+_w^ty=\begin{cases} x+y-2^w &\text{if }2^{w-1} \le x+y\\ x+y &\text{if }-2^{w-1}\le x+y <2^{w-1}\\ x+y+2^w&\text{if }x+y<-2^{w-1} \end{cases}x+wty=⎩⎪⎨⎪⎧x+y−2wx+yx+y+2wif 2w−1≤x+yif −2w−1≤x+y<2w−1if x+y<−2w−1
简单来说就是下溢加2w2^w2w,上溢减2w2^w2w。注意上溢做减法后为负数,下溢做加法后为非负数。此操作对于二进制码来说就是舍弃溢出的最高位。当下溢时,最高两位必是10,当上溢时,最高两位必是01。舍弃最高位就相当于加2w2^w2w和减2∗2w−12*2^{w-1}2∗2w−1,这也是刚才得出溢出后数值正负结论的原因。和无符号数一样,这一过程在计算机中实现很自然。
检测补码加法中的溢出:
不妨令s=x+wtys=x+_w^tys=x+wty,当且仅当x>0,y>0,s≤0x>0,y>0,s\le0x>0,y>0,s≤0时,发生正溢出;当且仅当x<0,y<0,s≥0x<0,y<0,s\ge0x<0,y<0,s≥0时,发生负溢出。下面看几段有趣的代码:
Ex1:
//检测溢出
int tadd_ok(int x, int y){int sum=x+y;return (sum-x==y)&&(sum-y==x);
}
显然将永远返回1,因为溢出的最高位没有影响,假设sum溢出,不妨把sum写成x+y−2wx+y-2^wx+y−2w,sum−x=y−2w=ysum-x=y-2^w=ysum−x=y−2w=y,同理sum−y=xsum-y=xsum−y=x。该例说明了补码计算的优越性。
Ex2:
//假设 tadd_ok能正确检测x+y是否溢出
int tsub_ok(x, -y){//检测x-y是否溢出return tadd_ok(x, -y);
}
错在哪?只因为一个特殊值TMinTMinTMin,即−2w−1-2^{w-1}−2w−1。因为由定义可知TMin=−TMinTMin=-TMinTMin=−TMin,取x=−1,y=TMinx=-1,y=TMinx=−1,y=TMin,将会判断错误。
定义补码的非:
−wtx={TMinif x=TMin−xif x>TMin-_w^tx=\begin{cases} TMin&\text{if }x=TMin\\ -x&\text{if }x>TMin \end{cases}−wtx={TMin−xif x=TMinif x>TMin
在位级运算上,补码的非运算有两种方法。第一是将所有位求补然后再加1,例如−6=0x6+1=0x9+1=0xA-6=~0x6+1=0x9+1=0xA−6= 0x6+1=0x9+1=0xA。第二可以将最右边1的左边所有位取反,例如−6=−[0110]=[1010]=0xA-6=-[0110]=[1010]=0xA−6=−[0110]=[1010]=0xA。
2.3.3 无符号乘法
∀x,y∈[0,2w−1],x∗y∈[0,22w−2w+1+1]\forall x,y\in[0,2^{w}-1],x*y\in[0,2^{2w}-2^{w+1}+1]∀x,y∈[0,2w−1],x∗y∈[0,22w−2w+1+1],可能需要2w个位来表示。方法如下:
x∗wuy=(x∗y)mod2wx*_w^uy=(x*y)\mod2^{w}x∗wuy=(x∗y)mod2w
就是进行简单的w位截断。
2.3.4 补码乘法
∀x,y∈[TMin,TMax]\forall x,y\in[TMin,TMax]∀x,y∈[TMin,TMax],定义x∗wty=U2Tw((x⋅y)mod2w)x*_w^ty=U2T_w((x\cdot y)\mod2^w)x∗wty=U2Tw((x⋅y)mod2w)
和无符号乘法类似,都是对溢出部分进行截断至w位。所以说补码乘法和无符号乘法在位级运算上表示相同。也就是说:
对于数字A、B,x和y是它们的补码表示,x′y′x'y'x′y′是它们的无符号表示,则T2Bw(x∗wty)=U2Bw(x′∗wty′)T2B_{w}(x*_w^ty)=U2B_{w}(x'*_w^ty')T2Bw(x∗wty)=U2Bw(x′∗wty′),这被称作位级等价性。
2.3.5 乘以常数
原理就是将一个数拆成2的幂相加减,从而将乘法转化为移位和加减运算。
首先乘以2的幂:
x=[xw−1,xw−2,…,x1,x0],∀k≥0,x∗2k=[xw−1,xw−2,…,x0,0,…,0]x=[x_{w-1},x_{w-2},…,x_{1},x_{0}],\forall k\ge0,x*2^k=[x_{w-1},x_{w-2},…,x_{0},0,…,0]x=[xw−1,xw−2,…,x1,x0],∀k≥0,x∗2k=[xw−1,xw−2,…,x0,0,…,0]。如果发生截断,则会变成[xw−k−1,xw−k−2,…,x0,0,…,0][x_{w-k-1},x_{w-k-2},…,x_{0},0,…,0][xw−k−1,xw−k−2,…,x0,0,…,0]。
可知乘2的幂可转化为移位运算,对于无符号乘法和补码乘法均有:
x∗wt2k=x<<kx*_w^t2^k=x<<kx∗wt2k=x<<k,x∗wu2k=x<<kx*_w^u2^k=x<<kx∗wu2k=x<<k。
那么接下来就是如何拆分常数的问题了。
任何数都可以表示成为0,10,10,1的串,其中我们只需要看只含1的子串。假设∀x=[1m,1m−1,…,1n+1]\forall x=[1_{m},1_{m-1},…,1_{n+1}]∀x=[1m,1m−1,…,1n+1],则x可表示为2m−2n2^m-2^n2m−2n或是2n+1+2n+2+…+2m2^{n+1}+2^{n+2}+…+2^{m}2n+1+2n+2+…+2m。
通常编译器会为我们优化到最佳分解,但我们也应知道具体的原理。
2.3.6 除以2的幂
和乘法类似,我们这次采用右移的方式。取0≤k<w0\le k<w0≤k<w
无符号除法比较简单,简单右移即可。注意无符号数采用逻辑右移。
⌊x/2k⌋=x>>k\lfloor x/2^k\rfloor=x>>k⌊x/2k⌋=x>>k
对于补码除法,稍显复杂。
首先,向下舍入,采用算术右移。
⌊x/2k⌋=x>>k\lfloor x/2^k\rfloor=x>>k⌊x/2k⌋=x>>k
由于之前我们分析过的补码算术右移,可以得知
[xw−1,xw−2,…,x1,x0]/2k=[xw−1,…,xw−1,……,xk][x_{w-1},x_{w-2},…,x_{1},x_{0}]/2^k=[x_{w-1},…,x_{w-1},……,x_{k}][xw−1,xw−2,…,x1,x0]/2k=[xw−1,…,xw−1,……,xk]
向上舍入,需要对位移运算稍作修改。
⌈x/2k⌉=(x+(1<<k)−1)>>k\lceil x/2^k\rceil=(x+(1<<k)-1)>>k⌈x/2k⌉=(x+(1<<k)−1)>>k
即(x+2k−1)/2k=⌈x/2k⌉(x+2^k-1)/2^k=\lceil x/2^k\rceil(x+2k−1)/2k=⌈x/2k⌉
不幸的是,常数除法不能像乘法那样拆成对2的幂的除法的和差。
2.4 浮点数
首先,对于浮点数IEEE(电气和电子工程师协会)在1985年左右推出了标准754,之后经过改进,得到了为所有计算机所共同接受的IEEE浮点标准。
2.4.1 二进制小数
考虑形如bmbm−1…b0.b−1b−2,…,b−nb_{m}b_{m-1}…b_{0}.b_{-1}b_{-2},…,b_{-n}bmbm−1…b0.b−1b−2,…,b−n的表示,则数b=∑i=−nmbi2ib=\sum_{i=-n}^mb_{i}2^ib=∑i=−nmbi2i
形如0.11……120.11……1_{2}0.11……12的数是刚好小于1的数,可以使用1−ε1-\varepsilon1−ε表示。
大部分小数是无法用二进制小数精确表示的。
2.4.2 IEEE浮点表示
刚才我们提到的定点表示法不能有效表达非常大的数字。由科学计数法,任何数都可以表示成x×2yx\times2^yx×2y的形式。我们希望确定x和y的规范来有效表达任意浮点数。
IEEE浮点标准形式如下:
V=(−1)s×M×2EV=(-1)^s\times M\times 2^EV=(−1)s×M×2E
其中各符号含义为:
s:符号(sign),s决定数字的正负,数值0的符号位解释将特殊处理。
M:尾数(significand),M是一个二进制小数,它的范围是1∼2−ε1\thicksim2-\varepsilon1∼2−ε,或是0∼1−ε0\thicksim1-\varepsilon0∼1−ε。
E:阶码(exponent),它的作用是对浮点数加权,权重为2E2^E2E
这些值在二进制码中编码顺序如下:sEM
一个单独的符号位s直接编码s,k位阶码字段exp=ek−1…e0exp=e_{k-1}…e_{0}exp=ek−1…e0编码阶码E,n位小数字段frac=fn−1…f1f0frac=f_{n-1}…f_{1}f_{0}frac=fn−1…f1f0编码尾数M。特殊的要注意M的值与E是否为0有关,不能直接使用。
单精度32位(1,8,23),双精度64位(1,11,52)
我们根据exp的值分为以下几种计算方式:
1.规格化的值
什么是规格化,就是exp既不为0,也不全为1情况。假设尾数有k位,我们增加一个偏置量Bias,令Bias=2k−1−1Bias=2^{k-1}-1Bias=2k−1−1。规定E=exp−BiasE=exp-BiasE=exp−Bias,由于exp∈[1,2k−1−2]exp\in[1,2^{k-1}-2]exp∈[1,2k−1−2],则E∈[1−Bias,2k−2−Bias]=[−2k−1+2,2k−1−1]E\in[1-Bias,2^k-2-Bias]=[-2^{k-1}+2,2^{k-1}-1]E∈[1−Bias,2k−2−Bias]=[−2k−1+2,2k−1−1]。例如float单精度,E∈[−126,+127]E\in[-126,+127]E∈[−126,+127];double双精度,E∈[−1022,+1023]E\in [-1022,+1023]E∈[−1022,+1023]。
小数字段frac用来表示小数值f,0≤f<10\le f<10≤f<1。规定M=1+fM=1+fM=1+f,它被称为隐含的以1开头的表示,这样可以增加一个精度位。我们总能控制1≤M<21\le M<21≤M<2。
2.非规格化的值
非规格化指阶码域全为0的情况。此时阶码值E=1−BiasE=1-BiasE=1−Bias,而尾数M=fM=fM=f,此时不含隐含的开头的1。注意到E≠BiasE\ne BiasE=Bias,为什么呢?是因为这样会提供由非规格化到规格化的一种平滑的变化,不会产生突变。
非规格化表示的用途如下:
1.提供0的表示
即所有位全为0,表示+0.0。然而有趣的一点是s=1,其他位全为0,此时得到-0.0。这点之后会详细解释。
2.表示那些非常接近0的数
非规格化提供了一种分布均匀地接近0的一种方式,即0,1/2k,2/2k,…,(2k−1)/2k0,1/2^{k},2/2^{k},…,(2^k-1)/2^k0,1/2k,2/2k,…,(2k−1)/2k。
3.特殊值
特殊值是指阶码全为1时的情况。当小数域frac全为0时,s=0时是正无穷,s=1时是负无穷,可以用来表示两个很大的数相乘溢出或除以0的结果。当小数域非0时,结果为NaN(Not a Number),不是一个数。当一些运算的结果不能是实数或无穷,就会返回这样的 NaN值。例如−1\sqrt{-1}−1、∞−∞\infty-\infty∞−∞等。也可以用它表示未初始化的数据。
举例说明(P81 Ex2.47)
假设一个基于IEEE浮点格式的5位浮点表示,有1个符号位、2个阶码位(k=2)和2个小数位(n=2)。那么Bias=2k−1−1=1Bias=2^{k-1}-1=1Bias=2k−1−1=1。填写下表:
位(s E M) | e | E | 2E2^E2E | f | M | 2E×M2^E\times M2E×M | V | 十进制 |
---|---|---|---|---|---|---|---|---|
0 00 00 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0.0 |
0 00 01 | 0 | 0 | 1 | 1/4 | 1/4 | 1/4 | 1/4 | 0.25 |
0 00 11 | 0 | 0 | 1 | 3/4 | 3/4 | 3/4 | 3/4 | 0.75 |
0 01 00 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 1.0 |
0 10 11 | 2 | 1 | 2 | 3/4 | 7/4 | 14/4 | 14/4 | 3.5 |
0 11 00 | - | - | - | - | - | - | ∞\infty∞ | - |
0 11 10 | - | - | - | - | - | - | NaN | - |
再考虑下述问题:(P83 Ex2.49)
如果一种具有n位小数的浮点格式,且有足够大的阶码字段长度,那么它所不能精确表示的最小正整数的公式是什么?
2n+1+12^{n+1}+12n+1+1。可以详细推导出结果。此时E=n+1,frac=[0,…,0,1]。
2.4.3 舍入
IEEE定义了四种舍入的方式,包括向偶数舍入、向零舍入、向下舍入和向上舍入。
向偶数舍入就是当数值正好处于中间时(即有效位数后为100…0),舍入后令最低有效位数是偶数(二进制中为0),这样可以保证在大多数现实情况下避免统计偏差。例如:10.110保留小数点后1位,末尾是10,舍入到偶数所以为11.0。而一般的像10.011就舍入到10.1即可,按照最近来舍入。
2.4.4 浮点运算
我们主要研究一下它的性质。浮点运算不同于整数运算,他不再是一个阿贝尔群。不是所有元素都有逆元,类似NaN,∞\infty∞。也不满足结合律,经典案例就是(3.14+1e10)−1e10=0,3.14+(1e10−1e10)=3.14(3.14+1e10)-1e10=0,3.14+(1e10-1e10)=3.14(3.14+1e10)−1e10=0,3.14+(1e10−1e10)=3.14。所以写代码就不要写成x=a+b+c;的形式,这样编译器为防止出错不会进行优化。另一方面,浮点运算能够满足单调性,这点是无符号数和补码加法所不具备的。
2.4.5 C中的浮点数
类型 | 转换后类型 | 效果 |
---|---|---|
int | float | 不会溢出,可能舍入 |
int、float | double | 能够保留精确数值 |
double | float | 值可能溢出成+∞+\infty+∞或−∞-\infty−∞,也可能舍入 |
float、double | int | 值将会向零舍入,也可能溢出 |
C中没有浮点转换到int的标准,所以遇到没有固定结果的情况就会返回一个[1,0,…,0],即TMin。例如(int)+1e10就会得到-2147483648(TMin)。
《深入理解计算机系统》(CSAPP)第二章——信息的表示和处理 知识点总结相关推荐
- 深入理解计算机系统(CSAPP) 第二章
家庭作业 2.57 借助 C++ 模板可以很方便的实现. // g++ -o main main.cc -std=c++11 #include <string> #include < ...
- 吾读 - 《深入理解计算机系统》第二章 信息的表示与处理 (二)浮点
浮点数绝对是计算机系统里最神秘的一种数值.在不了解它之前,很难想象同样是32位数值,int只能表示最大2的31次方,而浮点的表示范围却是-126到正的127次方. 以及浮点还能表示无穷大,NaN等特殊 ...
- 计算机系统中处理的信息是什么,《深入理解计算机系统》第二章 信息的表示和处理...
2.1信息存储 机器程序将内存视为一个很大的字节数组,称为虚拟内存.所有可能的地址集合称为虚拟地址空间,实际上,该功能的实现是将动态随机访问存储器(DRAM).闪存.磁盘存储器.特殊硬件和操作系统软件 ...
- 深入理解计算机系统_第二章_信息的表示和处理
深入,并且广泛-沉默犀牛 文章目录 文章导读 信息的表示和处理 信息存储 十六进制表示法 字数据大小 寻址和字节顺序 表示字符串 表示代码 布尔代数简介 C语言中的位级运算 C语言中的逻辑运算 C语言 ...
- 深入理解计算机系统_3e 第二章家庭作业 CS:APP3e chapter 2 homework
初始完成日期:2017.9.26 许可:除2.55对应代码外(如需使用请联系 randy.bryant@cs.cmu.edu),任何人可以自由的使用,修改,分发本文档的代码. 本机环境: (有一些需要 ...
- 深入理解计算机系统(第二版)第四章知识整理
深入理解计算机系统(第二版)笔记 第四章 处理器体系结构 4.1 Y86指令集体系结构 4.1.1 程序员可见的状态 4.1.2 Y86指令 4.1.3 指令编码 4.1.4 Y86异常 4.1.5 ...
- 深入理解计算机系统读书笔记(第二章 信息的表示和处理)
这里写自定义目录标题 第二章 信息的表示和处理 2.1 信息存储 2.1.1 十六进制表示法 2.1.2 字数据大小 2.1.3 寻址和字节顺序 2.1.4 表示字符串 2.1.5 代码表示 2.1. ...
- 计算机系统导论——读书笔记——第二章 信息的表示和处理(持续更新)
第二章 信息的表示和处理 2.1 信息存储 2.1.1 十六进制 2.1.2 字数据大小 2.1.3 寻址和字节顺序 1.地址:对象所使用的字节中最小的地址 2.大端法:最高有效字节在前 小端法:最低 ...
- 深入理解计算机系统——第12章 并发编程
深入理解计算机系统--第12章 并发编程 并发编程 如果逻辑控制流在时间上重叠,那么就称它们是并发的.注意:核心是在时间上重叠. 操作系统内核运行多个应用程序采用了并发机制,但并发不止用于内核,也用于 ...
最新文章
- windows系统下Python环境的搭建及Selenium的安装
- hbase shell-namespace(命名空间指令)
- 3.分布式文件系统HDFS之二
- linux下使用idl生成h文件,LINIUX下IDL的安装
- springboot报错---@RunWith(SpringRunner.class)
- 中相对路径与绝对路径的写法_相对路径和绝对路径?简洁易懂解释+实例
- telegram 机器人_学习使用Python在Telegram中构建您的第一个机器人
- Jquery与.net MVC结合,通过Ajax
- 向日葵显示无法连接到服务器,请修复电信卡使用向日葵远程无法连接服务问题!!!...
- 论高碳艺术与低碳艺术
- js-05--对象是什么、创建对象、对象使用、操作对象、遍历对象、内置对象、Math、任意范围随机数、日期对象、字符串对象
- ssm框架bean_Bean简介:简化的WordPress框架
- 查看文章影响因子的插件_查询文献可实时显示影响因子与分区排名的2个强大浏览器插件...
- 终极dos批处理循环命令详解
- 什么是 BigMap 算法
- 网络游戏安全小议(端游/页游/手游)
- IPS需意识到高级闪避技术(AET)的危害
- Java集合详解6:TreeMap和红黑树
- 操作文件操作符的工作模式:LT(电平触发)ET(边缘触发)实验对比
- 第一届PyCon China小记