深入理解计算机系统-阅读笔记

596 阅读4分钟

信息的表示和处理

前言

当某些计算结果太大而无法表示时,就会发生溢出。

整数的计算机运算满足人们所熟知的真正整数运算的许多性质。

信息存储

大多数计算机使用8位的块,或字节作为内存可寻址的最小单位,而不是通过访问单独的位来实现。对于字节和比特的关系,有:1字节(byte)=8(bit)1字节(byte) = 8位(bit)。机器级程序把内存视为超大数组来进行寻址处理。

程序对象存放在存储器里,程序对象一般包括程序数据,指令和控制信息。C语言中的一个指针的值,是某个存储块的第一个字节的虚拟地址。可以把程序对象视为一个或多个字节块,而程序本身就是一个字节序列。

十六进制表示法

为了方便记录,程序有时使用十六进制的表示法,在C语言中,以0x或0X开头的常量一般都是十六进制值。十六进制位0-9,A-F;对于字母部分,并不区分大小写。如果对于一个数据,它的位数是4的倍数,那么可以很容易把二进制形式的表示转换成十六进制的表示。

这里有一个对照表,可以供参考:

字数据大小

每台计算机都有一个字长,用于指明指针数据的标称大小,它也代表着系统虚拟地址最大大小。对于一个字长位ww位的机器而言,虚拟地址的范围是0~2w1{2^w}-1,程序最多访问2w2^w个字节。

寻址和字节顺序

多字节对象被存储为连续的字节序列,对象的地址是所使用的字节中最小的地址,也就是首地址。

对于一个ww位的整数来说,其位表示为 [xw1,xw2,,x1,x0][x_{w-1}, x_{w-2}, \cdots, x_1, x_0] ,其中xw1x_{w-1}为最高有效位,而x0x_0为最低有效位。假设ww是8的倍数,那么就可以把这些位分组成一个个字节,[xw1,xw2,,xw8][x_{w-1}, x_{w-2}, \cdots, x_{w-8}]为最高有效字节,[x7,x6,,x0][x_7, x_6, \cdots, x_0]为最低有效字节。

某些机器选择在内存中按照从最低有效位到最高有效位的顺序存储对象,这叫大端法,反之为小端法。假设变量x的类型是int十六进制为0x01234567,起始地址是0x100,那么大端小端表示方法分别为:

多数Intel兼容机使用小端法,iOS和Android也是用小端法。

关于大小端的区别,还是有的,比方说在网络传输方面,如果发送者和接收者不一致,就会发生倒序现象;第二种情况在于阅读字节序列时,最后一种则是在编写规避正常的类型系统的程序时。

表示字符串

文本数据相较于二进制数据,有更强的平台独立性。

表示代码

计算机系统的一个基本概念是,从机器的角度来看,程序仅仅是字节序列。

布尔代数简介

布尔运算有四种运算,非(~),且(&),或(|),异或(^)。它们所定义的运算是这样的:

而对于两个位向量的运算可以定义成它们的每个对应元素之间的运算。见C语言中的位级运算。

位向量的一个很有用的应用就是表示有限集合,可以使用位向量编码其任何子集,对于位向量[aw1,aw2,,a1,a0][a_{w-1}, a_{w-2}, \cdots, a_1, a_0],我们假设有一个集合AA {0,1,,w1}\subseteq \{ 0, 1, \cdots, {w-1} \},其中ai=1a_i=1当且仅当iAi\in A。当a=[01101001]a \overset{\cdot}{=} [ 01101001 ]。 时,有AA ={0,3,5}= \{ 0, 3, 5 \}

C语言中的位级运算

C语言的一个很有用的特性就是它支持按位进行布尔运算。直接看一个例子好了:

C语言中的逻辑运算

没什么好说的,就是表达式求值。

C语言中的移位运算

有两种移位方式,左移和右移,右移又分为算术右移和逻辑右移。

左移:对于一个操作数x,左移kk位意味着把x向左移kk位,丢弃最高的kk位,并在最右端(最低有效位方向)补0。如x=[xw1,xw2,,x1,x0]x = [x_{w-1}, x_{w-2}, \cdots, x_1, x_0 ] ,此时x << k结果为 x=[xwk1,xwk2,,x0,0,,0k]x = [x_{w-k-1}, x_{w-k-2}, \cdots, x_0, {\underbrace{0, \cdots, 0}_k } ] 所述。

来看看右移中的逻辑右移,它仅仅是在左端补0,而算术右移补最高有效位的值。对于操作x >> k的逻辑右移结果为 x=[0,,0k,xw1,xw2,,xk]x = [ \underbrace{0, \cdots, 0}_k, x_{w-1}, x_{w-2}, \cdots, x_k ] ,而对于算术右移的结果为 x=[xw1,,xw1k,xw2,,xk]x = [ \underbrace{x_{w-1}, \cdots, x_{w-1}}_k, x_{w-2}, \cdots, x_k] 。 对于所有的有符号数,几乎所有的编译器/机器都会使用算术右移;而对于无符号数,右移是逻辑右移。

对于k,如果kk很大,则取k=k mod wk = k \ mod \ {w}

整数表示

进行之前,请允许我在这里定义一些函数:

其中ww是数据位数。比如32位/64位。

整数数据类型

C语言支持很多整型数据类型,来看一下:

这是32位机的

这是64位机的

有一个很值得注意的地方就是,有符号数的取值范围不是对称的。负数要多一个。为此C语言定义了一个每种类型必须满足的取值范围:

除了固定长度的类型外,有符号数的负数和整数对称了。

无符号数编码

假设有一个整数数据类型有ww位,可以将位向量写成x\vec{x},或者写成[xw1,xw2,,x0][ x_{w-1}, x_{w-2}, \cdots, x_0]

x\vec{x}看成一个二进制表示的数,定义一个函数B2UwB2U_w表示把位长为ww的二进制数转换成无符号数。那么就可以得到如下表述:

无符号数编码的定义\because无符号数编码的定义

对于向量x=[xw1,xw2,,x0],有\therefore 对于向量 \vec{x} = [x_{w-1}, x_{w-2}, \cdots, x_0],有

B2Uw(x)=i=0w1xi2i B2U_w(\vec{x}) \overset{\cdot}{=} \sum_{i=0}^{w-1} x_i 2^i

来定义一下无符号数的最值。

UMaxw=i=0w12i=2w1UMax_w \overset{\cdot}{=} \sum_{i=0}^{w-1} 2^i = 2^w-1

UMinw=0UMin_w \overset{\cdot}{=} 0

二进制和无符号数之间的转换是一一对应的,反之亦然。

无符号数编码的唯一性 \because 无符号数编码的唯一性

函数B2Uw是一个双射 \therefore函数B2U_w是一个双射

或者可以这么说:x=U2Bw(B2Uw(x)) \vec{x} = U2B_w(B2U_w(\vec{x}))

补码编码(有符号数编码)

补码(Two's-Complement)编码用来表示有符号数,在补码编码里,将最高有效位解释成负权。

补码编码的定义\because 补码编码的定义

对于向量x=[xw1,xw2,,x0],有\therefore 对于向量\vec{x} = [x_{w-1}, x_{w-2}, \cdots, x_0],有

B2Tw(x)=xw12w1+i=0w2xi2iB2T_w(\vec{x}) \overset{\cdot}{=} -x_{w-1} 2^{w-1}+\sum_{i=0}^{w-2} x_i 2^i

最高有效位xw1x_{w-1}也称为符号位,它的权重为2w1-2^{w-1}

来看看补码定义的极值:

TMaxw=i=0w22i=2w11TMax_w \overset{\cdot}{=} \sum_{i=0}^{w-2} 2^i = 2^{w-1}-1

TMinw=2w1TMin_w \overset{\cdot}{=} -2^{w-1}

同时补码转换函数也是一个双射,原理是因为补码编码的唯一性。

x=T2Bw(B2Tw(x)) \vec{x} = T2B_w(B2T_w(\vec{x}))

来看一个比较重要的数值对比:

除了补码表示有符号数,还有两种方式,不过这两种方式表示0的方法不唯一。

反码(Ones' Complement):除了最高有效位的权是(2w11)-(2^{w-1}-1)而不是2w1-2^{w-1}之外,其他的和补码一致。

B2Ow()x=xw1(2w11)+i=0w2xi2iB2O_w()\vec{x} \overset{\cdot}{=} -x_{w-1} (2^{w-1}-1) + \sum_{i=0}^{w-2} x_i 2^i

原码:最高有效位是符号位,用来确定剩下的位应该取负权还是正权。

B2Sw(x)=(1)xw1(i=0w2xi2i)B2S_w(\vec{x}) \overset{\cdot}{=} (-1)^{x_w-1} (\sum_{i=0}^{w-2} x_i 2^i)

有符号数和无符号数之间的转换

强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。

来看看无符号数和有符号数之间的转换。

补码转换成无符号数\because 补码转换成无符号数 对满足TMinwxTMaxwx有:\therefore 对满足TMin_w \leq x \leq TMax_w的x有: T2Uw(x)={x+2wx<0xx0T2U_w(x)=\left\{ \begin{aligned} & x+2^w & x < 0 \\ & x & x \geq 0 \end{aligned} \right. 这是根据它们的定义得到的,好好想想补码和无符号数是怎么解释x\vec{x}的?!有一个,提一下,就是2w1+2w=2w1-2^{w-1}+2^{w} = 2^{w-1}

上述公式还可以化简,得到如下形式:

B2Uw(T2Bw(x))=T2Uw(x)=x+xw12wB2U_w(T2B_w(x)) = T2U_w(x) = x+x_{w-1} 2^w

当一个有符号数映射为它对应的无符号数时,负数就被转换成了大的正数,而非负数保持不变。

反过来看,如何把无符号数转换成补码。

无符号数转换成补码\because 无符号数转换成补码 对满足0uUMaxwu有:\therefore 对满足0\leq u \leq UMax_w的u有: U2Tw(u)={uuTMaxwu2wu>TMaxwU2T_w(u) = \left\{ \begin{aligned} & u & u \leq TMax_w \\ & u-2^w & u > TMax_w \end{aligned} \right.

上述代码可以化简如下:

U2Tw(u)=uw12w+uU2T_w(u) = -u_{w-1} 2^w + u

C语言中的有符号数和无符号数

C语言允许进行无符号数和补码之间的转换,虽然C语言没有明确地指明如何进行这种转换,但是一般的原则都是,保持底层的位不变而仅仅更改解释方法。

对于一个运算,如果含有无符号数和补码,那么会全部转换成无符号数来处理。来看一些例子来理解:

非直观的情况标注了*。

扩展一个数字的位表示

想要把一个无符号数扩展成更大的位,仅仅简单的在前面加0即可。

无符号数的零扩展\because 无符号数的零扩展

定义宽度位w的位向量u=[uw1,uw2,,u0]\therefore 定义宽度位w的位向量 \vec{u} = [u_{w-1}, u_{w-2}, \cdots, u_0] 和宽度位w的位向量u=[0,0,,0,uw1,uw2,,u0],其中w>w和宽度位w^{\prime}的位向量\vec{u^{\prime}}=[0, 0, \cdots, 0, u_{w-1}, u_{w-2}, \cdots, u_0],其中w^{\prime} > w。 则有B2Uw(u)=B2Uw(u)则有B2U_w(\vec{u}) = B2U_{w^{\prime}}(\vec{u^{\prime}})

要将一个补码数字转换为一个更大的数据,可以执行符号扩展,在表示中添加最高有效位的值,这有点像算术右移。

补码数的符号扩展\because 补码数的符号扩展

定义长度为w的位向量x=[xw1,xw2,,x0]\therefore 定义长度为w的位向量 \vec{x} = [x_{w-1}, x_{w-2}, \cdots, x_0] 和宽度为w的位向量x=[xw1,,xw1,xw2,x0],其中w>w和宽度为w^{\prime}的位向量\vec{x^{\prime}} = [x_{w-1}, \cdots, x_{w-1}, x_{w-2}, x_0],其中w^{\prime} > w。 B2Tw(x=B2Tw(x)则B2T_w(\vec{x}) = B2T_{w^{\prime}}(\vec{x^{\prime}})

来证明一个东西,首先令w=w+kw^{\prime} = w+k,试证明 B2Tw+k([xw1,,xw1k,xw1,xw2,,x0])=B2Tw([xw1,xw2,,x0])B2T_{w+k}([ \underbrace{x_{w-1}, \cdots, x_{w-1}}_k, x_{w-1}, x_{w-2}, \cdots, x_0]) = B2T_w([x_{w-1}, x_{w-2}, \cdots, x_0])

在这里想要证明这个,使用关键属性2w1+2w=2w1-2^{w-1}+2^w = 2^{w-1}

贴上递归证明过程:

截断数字

当把一个ww位的数x=[xw1,xw2,,x0]\vec{x} = [x_{w-1}, x_{w-2}, \cdots, x_0]截断为一个kk位数字时,我们会丢弃高wkw-k位,得到一个新的位向量x=[xk1,xk2,,x0]\vec{x^{\prime}} = [x_{k-1}, x_{k-2}, \cdots, x_0]

对于无符号数,可以很容易得到其结果:

截断无符号数\because 截断无符号数 x=[xw1,xw2,,x0],而x是将其截断为k位的结果\therefore 令\vec{x}=[x_{w-1}, x_{w-2}, \cdots, x_0],而\vec{x^{\prime}}是将其截断为k位的结果 x=[xk1,xk2,,x0]。令x=B2Uw(x)x=B2Uk(x)\vec{x^{\prime}} = [x_{k-1}, x_{k-2}, \cdots, x_0]。令x = B2U_w(\vec{x}),x^{\prime} = B2U_k(\vec{x^{\prime}})。 x=x mod 2k则x^{\prime} = x \ mod \ 2^k。

来看一个解释:

在这里使用了属性对于任意的ik2i mod 2k=0对于任意的i \geq k,2^i \ mod \ 2^k = 0

补码截断也有类似的属性,只不过需要把最高位转换成符号位。

截断补码数值\because 截断补码数值

x=[xw1,xw2,,x0],而x是将其截断为k位的结果\therefore令\vec{x}=[x_{w-1}, x_{w-2}, \cdots, x_0],而\vec{x^{\prime}}是将其截断为k位的结果

x=[xk1,xk2,,x0]。令x=B2Uw(x)x=B2Tk(x)\vec{x^{\prime}} = [x_{k-1}, x_{k-2}, \cdots, x_0]。令x = B2U_w(\vec{x}),x^{\prime} = B2T_k(\vec{x^{\prime}})。

x=U2Tk(x mod 2k)则x^{\prime} = U2T_k(x \ mod \ 2^k)。

来看一个化简:

B2Uw([xw1,xw2,,x0]) mod 2k=B2Uk([xk1,xk2,,x0])B2U_w([x_{w-1}, x_{w-2}, \cdots, x_0]) \ mod \ 2^k = B2U_k([x_{k-1}, x_{k-2}, \cdots, x_0])

也就是x mod 2kx \ mod \ 2^k可以被表示为[xk1,xk2,,x0][x_{k-1}, x_{k-2}, \cdots, x_0]的无符号数表示。将其转换成补码数有x=U2Tk(x mod 2k)x^{\prime} = U2T_k(x \ mod \ 2^k)

总而言之,无符号数阶段结果是:

B2Uk([xk1,xk2,,x0])=B2Uw([xw1,xw2,,x0]) mod 2kB2U_k([x_{k-1}, x_{k-2}, \cdots, x_0]) = B2U_w([x_{w-1}, x_{w-2}, \cdots, x_0]) \ mod \ 2^k

而补码数字的截断结果是:

B2Tk([xk1,xk2,,x0])=U2Tk(B2Uw([xw1,xw2,,x0]) mod 2k)B2T_k([x_{k-1}, x_{k-2}, \cdots, x_0]) = U2T_k(B2U_w([x_{w-1}, x_{w-2}, \cdots, x_0]) \ mod \ 2^k)

有符号数和无符号数的建议

整数运算

无符号加法

对于两个无符号数xxyy,满足0x,y2w10 \leq x, y \leq 2^{w}-1。它们都是ww位的数。然而它们的和的范围是0x+y2w+120 \leq x+y \leq 2^{w+1}-2。所以可能会需要w+1w+1位来存储。如果结果的ww位(因为从0开始,所以其实是第w+1w+1位)非0,那么可能要舍弃,这就发生了溢出。

在此,来定义无符号数加法:+wu+_w^u

无符号数加法\because 无符号数加法 对满足0x,y2w1x,y有:\therefore 对满足0 \leq x, y \leq 2^w-1的x, y有: x+wuy={x+y,x+y2w1正常x+y2w,2wx+y2w+12溢出x +_w^u y = \left\{ \begin{aligned} & x + y, & & x + y \leq 2^w-1 & 正常 \\ & x + y -2^w, & & 2^w \leq x + y \leq 2^{w+1}-2 & 溢出 \end{aligned} \right.

一般而言,如果x+y<2wx+y < 2^w,和的第w+1w+1位会是0,所以即使丢弃也不会影响;另一方面,如果2wx+y<2w+12^w \leq x+y < 2^{w+1},和的w+1w+1等于1,因此丢弃它就像减去了2w2^w一样。

说一个算数运算溢出,是指完整的整数结果没法放到数据类型的字长限制中去。

对于溢出,需要想办法把它检测到,C语言程序并不会发出溢出信号,因此需要自己检测。

检测无符号数加法中的溢出\because 检测无符号数加法中的溢出

对在范围0x,yUMaxw中的x,y,令s=x+wuy\therefore 对在范围0 \leq x, y \leq UMax_w中的x, y,令s \overset{\cdot}{=} x +_w^u y。 则对计算s,当且仅当s<x(s<y)时,计算发生了溢出。则对计算s,当且仅当s < x(或s < y)时,计算发生了溢出。

对于减法运算,可以运用阿贝尔群来求得被减数的逆元,进而完成运算。

无符号数求反\because 无符号数求反 对满足0x2w1的任意x,其w位的无符号逆元wux可由下式给出:\therefore 对满足0 \leq x \leq 2^w-1的任意x,其w位的无符号逆元-_w^u x可由下式给出: wux={x,x=02wx,x>0-_w^u x = \left\{ \begin{aligned} & x, & & x = 0 \\ & 2^w-x, & & x > 0 \end{aligned} \right.

减去一个数等于加上这个数的逆元。

有符号加法(补码加法)

对于补码加法,必须确定当结果太大或结果太小时,应该做些什么。

对于x,yx, y,有2w1x,y2w11-2^{w-1} \leq x, y \leq 2^{w-1}-1。则它们的和的范围可能是2wx+y2w2-2^w \leq x+y \leq 2^w-2之内。想要结果精确,可能需要w+1w+1位。就像以前一样,依旧得截断为ww位。在此,定义x+wtyx+_w^ty为整数x+yx+y被截断为ww位的结果,并将这个结果看成补码数。

补码加法\because 补码加法

对满足2w1x,y2w11的整数xy,有:\therefore 对满足-2^{w-1} \leq x, y \leq 2^{w-1}-1的整数x和y,有:

x+wty={x+y2w,2w1x+y正溢出x+y,2w1x+y<2w1正常x+y+2w,x+y<2w1负溢出x+_w^ty = \left\{ \begin{aligned} & x + y - 2^w, & & 2^{w-1} \leq x + y & & 正溢出 & \\ & x + y,& &-2^{w-1} \leq x + y < 2^{w-1}& &正常& \\ & x + y + 2^w,& &x + y < -2^{w-1}& &负溢出& \end{aligned} \right. 在这里解释一下,首先看正溢出,为什么要2w-2^w呢?因为这个范围内的计算会造成最高有效位为1(此时位长w+1w+1),所以真实的结果是右ww位加起来2w-2^w,不过由于截断刚好只能得到右ww位的值,此时减去2w2^w刚好是答案,所以就像保持了原本的结果一样,一切刚刚好!负溢出同理。

case1对应02w10-2^{w-1};case4对应2w10-2^{w-1}-0

来看一个推论吧!

x+wty=U2Tw(T2Uw(x)+wuT2Uw(y))x+_w^ty \overset{\cdot}{=} U2T_w(T2U_w(x)+_w^u T2U_w(y)) 然后还可以化简一下,对于T2Uw(x)T2U_w(x)换成xw12w+xx_{w-1} 2^w+x,把T2Uw(y)T2U_w(y)换成yw12w+yy_{w-1} 2^w + y。然后就能得到:

要知道xw12wx_{w-1} 2^wyw12wy_{w-1} 2^w这两项,模2w2^w都是0.

此时,是时候看看怎么检测补码的溢出了。

检测补码加法中的溢出\because 检测补码加法中的溢出

对满足TMinwx, yTMaxwxy,令s=x+wty对满足TMin_w \leq x, \ y \leq TMax_w的x和y,令s \overset{\cdot}{=} x+_w^ty。

当且仅当x>0, y>0,但s0时,计算s发生了正溢出。当且仅当x > 0, \ y > 0,但s \leq 0时,计算s发生了正溢出。 当且仅当x<0, y<0,但s0时,计算s发生了负溢出。当且仅当x < 0, \ y < 0,但s \geq 0时,计算s发生了负溢出。

补码的非

对于补码,也可以定义其逆元来完成减法运算。

补码的非\because 补码的非

对满足TMinwxTMaxwx,其补码的非wtx由下面的式子给出:对满足TMin_w \leq x \leq TMax_w的x,其补码的非-_w^t x由下面的式子给出:

wtx={TMinw,x=TMinwx,x>TMinw-_w^t x = \left\{ \begin{aligned} & TMin_w, & & x = TMin_w \\ & -x, & & x > TMin_w \end{aligned} \right.

对于补码非得位级表示,其实还有别的方法,第一种方法是各位取反,末尾+1,第二种是从右向左找到第一个1得位置,对这个1左边所有的位取反。

第一种

第二种

无符号乘法

对于两个无符号数x, yx, \ y,它们的范围是0x, y2w10 \leq x, \ y \leq 2^w-1。那么它们的乘积的范围可以是0(2w1)20-(2^w-1)^2。这可能需要2w2w位来表示。所以还得截断就是了。定义乘积位xwuyx *_w^u y

将一个无符号数截断到ww位相当于模2w2^w

无符号数乘法\because 无符号数乘法

对满足0x, yUMaxwx, y有:对满足0 \leq x, \ y \leq UMax_w的x, \ y 有:

xwuy=(xy) mod 2wx *_w^u y = (x \cdot y) \ mod \ 2^w

补码乘法(有符号乘法)

对于两个补码数x, yx, \ y,它们的范围是2w1x, y2w11-2^{w-1} \leq x, \ y \leq 2^{w-1}-1,则它们的乘积的范围是2w1(2w11)22w2-2^{w-1} \cdot (2^{w-1}-1)至2^{2w-2}之间。当然也是需要截断为w位的。把一个补码数截断为w位相当于先计算该值模2w2^w,再把无符号数转换成补码数。

补码乘法\because 补码乘法

对满足TMinwx, yTMaxwx, y有:对满足TMin_w \leq x, \ y \leq TMax_w的x, \ y有:

xwty=U2Tw((xy) mod 2w)x *_w^t y = U2T_w((x \cdot y) \ mod \ 2^w)

对于无符号数和补码数来说,乘法运算的位级表示都是一样的,所以可以使用无符号数的乘法处理有符号数的乘法,具体细节如下:

无符号数乘法和补码乘法的位级等价性。\because 无符号数乘法和补码乘法的位级等价性。

给定长度为的w的位向量xy给定长度为的w的位向量\vec{x},\vec{y}。

用补码形式定义整数x,y,其中x=B2Tw(x)y=B2Tw(y)用补码形式定义整数x, y,其中x=B2T_w(\vec{x}),y=B2T_w(\vec{y})。

用无符号形式定义整数x,y,则x=B2Uw(x)y=B2Uw(y)用无符号形式定义整数x^{\prime}, y^{\prime},则x^{\prime} = B2U_w(\vec{x}),y^{\prime} = B2U_w(\vec{y})。

则有T2Bw(xwty)=U2Bw(xwuy)则有T2B_w(x *_w^t y) = U2B_w(x^{\prime} *_w^u {y^{\prime}})

对于无符号数和补码,它们在进行乘法运算之后,再截断,位级表示都是一样的!

现在来推导一下无符号数和补码数的乘法的位级等价性。

因为有x=x+xw12wx^{\prime} = x+x_{w-1}2^wy=y+yw12wy^{\prime} = y+y_{w-1}2^w,所以有:

由于模运算,所以可以化简得此。再加上xwty=U2Tw((xy) mod 2w)x *_w^t y = U2T_w((x \cdot y) \ mod \ 2^w)。对两边同时应用T2UwT2U_w,有:

T2U(xwty)=T2Uw(U2Tw((xy) mod 2w))=(xy) mod 2wT2U(x *_w^t y) = T2U_w(U2T_w((x \cdot y) \ mod \ 2^w)) = (x \cdot y) \ mod \ 2^w 代入(xy) mod 2w=(xy) mod 2w(x^{\prime} \cdot y^{\prime}) \ mod \ 2^w = (x \cdot y) \ mod \ 2^w得:

U2Bw(T2Uw(xwty))=T2B(xwty)=U2Bw(xwuy) U2B_w(T2U_w(x *_w^t y)) = T2B(x *_w^t y) = U2B_w(x^{\prime} *_w^u y^{\prime})

乘以常数(乘法优化)

对于乘法,往往需要更多的时钟周期,而加法,减法,移位等只需要一个时钟周期,因此试着把常数乘法拆解为加法移位等组合运算会快一些。

在C语言中,左移kk位相当于乘了2k2^k。所以对于一个常数,可以考虑把它分解成多个2的幂相加的形式。

除以2的幂

除法相对于乘法则更慢,需要其三倍的时间甚至更多。所以对于除法也可以考虑进行优化操作。

无符号数和补码数可以分别通过逻辑移位和算术移位来达到目的。

整数除法总是舍入到0,为此,定义一些操作,a\lceil a \rceil是向上取整,使得存在bb使得b1<abb-1 < a \leq b比如3.14=4; 3.14=3\lceil 3.14 \rceil = 4; \ \lceil -3.14 \rceil = 3;而a\lfloor a \rfloor是向下取整,使得存在bb使ba<b+1b \leq a < b+1,比如3.14=3; 3.14=4\lfloor 3.14 \rfloor = 3; \ \lfloor -3.14 \rfloor = -4

对于无符号数的右移,是比较简单的,因为它仅涉及到逻辑右移。

除以2的幂的无符号数除法\because 除以2的幂的无符号数除法

C语言变量xk分别有无符号数值xk,且0k<w\therefore C语言变量x和k分别有无符号数值x和k,且0\leq k < w。

C语言表达式x>>k产生数值x/2k则C语言表达式x >> k产生数值\lfloor x/2^k \rfloor。

对于补码数的操作可能稍微复杂。当补码数位非负数时,操作和无符号数一样,但是当补码数为负数时,需要进行偏置并向上取整。

C语言变量xk分别有补码数值和无符号数值xk,且0k<wx<0C语言变量x和k分别有补码数值和无符号数值x和k,且0 \leq k < w,x < 0。

则当执行算数移位时,C语言表达式(x+(1<<k)1)>>k产生数值x/2k则当执行算数移位时,C语言表达式(x+(1 << k) -1) >> k 产生数值\lfloor x/2^k \rfloor

偏置技术使用了这个原理:对于整数xy(y>0)x/y=(x+y1)/y对于整数x和y(y>0),\lceil x/y \rceil = \lfloor (x+y-1)/y \rfloor

于是在使用算术右移的补码机器,C语言表达式(x<0(x<0 x+(1<<k)-1 : x) >> k会计算数值会计算数值x/2^k$。C语言默认向下舍入。

浮点数

二进制小数

考虑一个形如bmbm1b1b0.b1b2bn+1bnb_mb_{m-1} \cdots b_1b_0 . b_{-1}b_{-2} \cdots b_{-n+1}b_{-n}的表示法,其中每个二进制数字,或者称为位,bib_i的取值范围是0和1。这种表示方法表示的数bb定义如下:

b=i=nm2i×bib = \sum_{i = -n}^{m}2^i \times b_i

二进制小数小数点左移一位相当于除2,右移一位相当于乘2。注意,像0.11111111111112{0.1111111111\cdots111}_2这样的小数是刚好小于1的数。

小数的二进制法只能表示那些能写成x×2yx \times 2^y形式的数。增长二进制表示的长度可以提高表示的精度。

IEEE浮点数表示

IEEE浮点数标准使用V=(1)s×M×2EV=(-1)^s \times M \times 2^E来表示一个数

来看看它的每位的意义:

1. 符号位(s)决定这个数是正数(0)还是负数(1)
2. 尾数(M)是一个二进制小数,它的范围是1-(2-x)或0-(1-x)
3. 阶码(E)的作用是对浮点数加权,这个权重是2的E次幂(可能是负数)

将一个浮点数的位划分为三个字段,分别进行编码:

1. 一个单独的符号位
2. k长度的位,用来编码阶码
3. n长度的位,用来记录二进制小数

在C语言中,32位的float,符号位占1位, k=8, n=23,在64位的double中,符号位占1位, k=11, n=52。

对于浮点数形式,来看看一些情况包括格式化的和非格式化的。

来看看他们的情况吧!

首先是规格化的数,这是最普遍的情况,当exp的位模式既不全是0(对应的值是0)也不全是1时(对应的值是255,双精度是2047),指的就是这种情况。不过为了能表示负权值,我们可以使用偏置值来实现,也就是说阶码值是E=exp-Bias(单精度是127,双精度是1023),由此产生的偏执范围是(-126~+127),双精度是(-1022~+1023)。

小数字段frac被解释为描述小数值ff,其中0f<10 \leq f < 1,其二进制表示为0.fn1f1f00.f_{n-1}\cdots f_1f_0,也就是二进制小数点在最高有效位的左边。尾数M定义为M=1+fM=1+f,因此可以把MM看成一个二进制表达式为1.fn1f1f01.f_{n-1}\cdots f_1f_0的数字。既然第一位可以通过调整E来使他为1,那么可以隐去不表,这样就又多了一个精度来记录值。

其次是非规格化的情况,当阶码域全为0时,所表示的数就是非规格化的情况,此时E=1-Bias,而尾数的值M=fM=f,也就是小数字段的值。不包含隐含的开头的1。

非规格化值有两个用途,首先它提供了一种表示0的方法,因为规格化数M一定大于1。不过还有正负0的区别哈哈哈哈!另一个用途就是可以表示那些非常接近0.0的数,它们提供了一种属性,称为逐渐溢出,其中可能的数值分布均匀地接近于0.0。

对于特殊值,最后一类是指阶码域全为1时的情况,当小数域全为0时,得到的就是无穷,当然有正负之分咯(符号位决定)!无穷可以用来表示溢出结果,比如很大的数相乘,或者除以0时。如果小数域非0,那就是NaN,非数。

数字示例

略略略,累死了累死了!别忘了看,不写可以。

舍入

浮点数使用向偶舍入的方式来处理舍入,啥意思呢?就是如果想要舍入的值小于精度后面全为0的那个参考值的话,直接丢弃,如果大于直接+1,如果等于,舍入到偶数那里,屮没表达清楚,看例子好了:

舍入到小数点后两位:1.2349999->1.23; 1.2350000->1.24; 1.2350001->1.24; 1.245000->1.24

而在二进制里,规定0时偶数,1是计数,参考值是1000...所以假设舍入到小数点后x位,那么看x+1到末尾,如果组成是1000000000...那么久看x位是0是1,是1就在x位+1(然后该进位进位),否则为0就不动。如果是10..0010...0反正只要大于10000...就直接x位+1;小于就不动。

还是舍入到后两位,不过是二进制小数:10.00011->10.00; 10.00110->10.01; 10.11100->11.00; 10.10100->10.10。

浮点运算

整数加法有阿贝尔群,其实现实中的实数加法也有阿贝尔群,但是在处理计算机小数时,必须考虑舍入的影响。

浮点数加法不具有结合性,这是缺少的最重要的群属性。

C语言中的浮点数

啊这!好像没什么好说的,就float和double的用法。