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)=x0​20+x1​21+……+xw−1​2w−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−1​2w−1+xw−2​2w−2+……+x0​20
原理:补码编码的唯一性,函数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−2​2w−2+……+x0​20
原码: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−2​2w−2+……+x0​20)
反码=补码+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+wu​y={x+yx+y−2w​if x+y<2wif 2w≤x+y<2w+1​
溢出就减去2w2^w2w即可,想想也比较合理,就是去掉结果溢出的最高位1得到的值。
那么如何检测程序中是否发生溢出呢?
不妨令s=x+wuys=x+_w^uys=x+wu​y,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}−wu​x={x2w−x​if 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+wt​y=⎩⎪⎨⎪⎧​x+y−2wx+yx+y+2w​if 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+wt​y,当且仅当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}−wt​x={TMin−x​if 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∗wu​y=(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∗wt​y=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∗wt​y)=U2Bw​(x′∗wt​y′),这被称作位级等价性。

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∗wt​2k=x<<k,x∗wu2k=x<<kx*_w^u2^k=x<<kx∗wu​2k=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}bm​bm−1​…b0​.b−1​b−2​,…,b−n​的表示,则数b=∑i=−nmbi2ib=\sum_{i=-n}^mb_{i}2^ib=∑i=−nm​bi​2i
形如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​…f1​f0​编码尾数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)第二章——信息的表示和处理 知识点总结相关推荐

  1. 深入理解计算机系统(CSAPP) 第二章

    家庭作业 2.57 借助 C++ 模板可以很方便的实现. // g++ -o main main.cc -std=c++11 #include <string> #include < ...

  2. 吾读 - 《深入理解计算机系统》第二章 信息的表示与处理 (二)浮点

    浮点数绝对是计算机系统里最神秘的一种数值.在不了解它之前,很难想象同样是32位数值,int只能表示最大2的31次方,而浮点的表示范围却是-126到正的127次方. 以及浮点还能表示无穷大,NaN等特殊 ...

  3. 计算机系统中处理的信息是什么,《深入理解计算机系统》第二章 信息的表示和处理...

    2.1信息存储 机器程序将内存视为一个很大的字节数组,称为虚拟内存.所有可能的地址集合称为虚拟地址空间,实际上,该功能的实现是将动态随机访问存储器(DRAM).闪存.磁盘存储器.特殊硬件和操作系统软件 ...

  4. 深入理解计算机系统_第二章_信息的表示和处理

    深入,并且广泛-沉默犀牛 文章目录 文章导读 信息的表示和处理 信息存储 十六进制表示法 字数据大小 寻址和字节顺序 表示字符串 表示代码 布尔代数简介 C语言中的位级运算 C语言中的逻辑运算 C语言 ...

  5. 深入理解计算机系统_3e 第二章家庭作业 CS:APP3e chapter 2 homework

    初始完成日期:2017.9.26 许可:除2.55对应代码外(如需使用请联系 randy.bryant@cs.cmu.edu),任何人可以自由的使用,修改,分发本文档的代码. 本机环境: (有一些需要 ...

  6. 深入理解计算机系统(第二版)第四章知识整理

    深入理解计算机系统(第二版)笔记 第四章 处理器体系结构 4.1 Y86指令集体系结构 4.1.1 程序员可见的状态 4.1.2 Y86指令 4.1.3 指令编码 4.1.4 Y86异常 4.1.5 ...

  7. 深入理解计算机系统读书笔记(第二章 信息的表示和处理)

    这里写自定义目录标题 第二章 信息的表示和处理 2.1 信息存储 2.1.1 十六进制表示法 2.1.2 字数据大小 2.1.3 寻址和字节顺序 2.1.4 表示字符串 2.1.5 代码表示 2.1. ...

  8. 计算机系统导论——读书笔记——第二章 信息的表示和处理(持续更新)

    第二章 信息的表示和处理 2.1 信息存储 2.1.1 十六进制 2.1.2 字数据大小 2.1.3 寻址和字节顺序 1.地址:对象所使用的字节中最小的地址 2.大端法:最高有效字节在前 小端法:最低 ...

  9. 深入理解计算机系统——第12章 并发编程

    深入理解计算机系统--第12章 并发编程 并发编程 如果逻辑控制流在时间上重叠,那么就称它们是并发的.注意:核心是在时间上重叠. 操作系统内核运行多个应用程序采用了并发机制,但并发不止用于内核,也用于 ...

最新文章

  1. windows系统下Python环境的搭建及Selenium的安装
  2. hbase shell-namespace(命名空间指令)
  3. 3.分布式文件系统HDFS之二
  4. linux下使用idl生成h文件,LINIUX下IDL的安装
  5. springboot报错---@RunWith(SpringRunner.class)
  6. 中相对路径与绝对路径的写法_相对路径和绝对路径?简洁易懂解释+实例
  7. telegram 机器人_学习使用Python在Telegram中构建您的第一个机器人
  8. Jquery与.net MVC结合,通过Ajax
  9. 向日葵显示无法连接到服务器,请修复电信卡使用向日葵远程无法连接服务问题!!!...
  10. 论高碳艺术与低碳艺术
  11. js-05--对象是什么、创建对象、对象使用、操作对象、遍历对象、内置对象、Math、任意范围随机数、日期对象、字符串对象
  12. ssm框架bean_Bean简介:简化的WordPress框架
  13. 查看文章影响因子的插件_查询文献可实时显示影响因子与分区排名的2个强大浏览器插件...
  14. 终极dos批处理循环命令详解
  15. 什么是 BigMap 算法
  16. 网络游戏安全小议(端游/页游/手游)
  17. IPS需意识到高级闪避技术(AET)的危害
  18. Java集合详解6:TreeMap和红黑树
  19. 操作文件操作符的工作模式:LT(电平触发)ET(边缘触发)实验对比
  20. 第一届PyCon China小记

热门文章

  1. arm9 中断向量 重定位_STM32中断向量表的位置,重定向
  2. ERP实施跨越五大难题(Zt)
  3. fpga实现YCbCr444转RGB
  4. 这是2007年的典型年终陈词滥调,其中包含统计数据,图表和无用数字
  5. php yii框架路由,yii框架如何配置路由
  6. linux uniq 源码,Linux uniq命令详解
  7. 人物传记《当今奇人周兴和》一 地窖出生的孩子
  8. lle算法c 语言,LLE算法示意图.ppt
  9. C语言——如何调用函数
  10. 2023世界人工智能大会 | 智能媒体计算专题论坛