本文地址:https://www.cnblogs.com/faranten/p/16099916.html
转载请注明作者与出处
在计算机中,数字分为定点数和浮点数两类,“定点”的含义为小数点的位置是固定的,“浮点”则意味着小数点的位置不固定。简单起见,定点数分为纯小数和纯整数,如果有一个数\(x\)在计算机中的存储为\(x_nx_{n-1}\cdots x_1x_0\),则
整型数据就是用定点数的方式表示的。
如果要让数据具有正负性,有多种处理方式,一种最常见的方式就是原码,它的思路就是单独留出一个二进制位(比特)来表示符号,且该位不具备权重,即对于一个真值\(x\)而言,它在计算机中的原码表示为\([x]_{\text{原}}\):
而反码则是利用了实数轴的对称性,真值\(x\)在计算机中的反码表示为\([x]_{\text{反}}\):
补码则是将最高位视为一个特殊的带权位,真值\(x\)在计算机中的补码表示为\([x]_{\text{补}}\):
其中\(\rightharpoondown\)表示对二进制中的每一位进行取反操作。上面的三个式子给出了从真值\(x\)生成机器表示的方法,下面给出从机器表示\(x_nx_{n-1}\cdots x_1x_0\)反解出真值的方法:
其中\(\oplus\)为异或运算。下面的表格给出了这三个表示方式的差别(以\(8\)个比特为例):
| 计算机中存储形式 | 原码\(\rightarrow\)真值 | 反码\(\rightarrow\)真值 | 补码\(\rightarrow\)真值 |
|---|---|---|---|
| \(0000~0000\) | \(+0\) | \(+0\) | \(0\) |
| \(0000~0001\) | \(+1\) | \(+1\) | \(+1\) |
| \(0000~0010\) | \(+2\) | \(+2\) | \(+2\) |
| \(\dots\) | \(\dots\) | \(\dots\) | \(\dots\) |
| \(0111~1110\) | \(+126\) | \(+126\) | \(+126\) |
| \(0111~1111\) | \(+127\) | \(+127\) | \(+127\) |
| \(1000~0000\) | \(-0\) | \(-127\) | \(-128\) |
| \(1000~0001\) | \(-1\) | \(-126\) | \(-127\) |
| \(\dots\) | \(\dots\) | \(\dots\) | \(\dots\) |
| \(1111~1101\) | \(-125\) | \(-2\) | \(-3\) |
| \(1111~1110\) | \(-126\) | \(-1\) | \(-2\) |
| \(1111~1111\) | \(-127\) | \(-0\) | \(-1\) |
可以看出,不论是什么存储方式,最高位始终可以起到标志正负的符号位作用。
至于移码,这只是一个存储上的技巧而已,主要用于比较大小,真值\(e\)在计算机中的移码表示为\([e]_{\text{移}}\):
其中\(e\)本身采用补码表示,即\(-2^k\leq e\leq2^k-1\),则\(0\leq e\leq2^{k+1}-1\),相当于添加了一个偏置使其为正数,从硬件上来说,移码更容易直接比较大小。
定点数能够表示的范围是有限的,且定点数在它表示范围内的不同区域中的精度是一样的。考虑到任何数都能写为
通过下面的定义
| 阶符 | 阶码 | 数符 | 尾数 |
|---|---|---|---|
| \(E_s\) | \(E_{m-1}\cdots E_1E_0\) | \(M_s\) | \(M_{n-1}\cdots M_1M_0\) |
就能表示浮点数,其中阶符和数符分别表示阶码和尾数的正负。并且可以发现,在不同区域,这样定义的浮点数表示的数字的精度是不同的,精度取决于尾数的有效位数。在实际应用中,我们用 IEEE 754 标准定义浮点数,这包括单精度 float 浮点数和双精度 double 浮点数:
| 31 | 30 - 23 | 22 - 0 |
|---|---|---|
| 符号位\(S\) | 阶码\(E=e+\text{Bias}_{32}\) | 尾数\(M\) |
| 63 | 62 - 52 | 51 - 0 |
|---|---|---|
| 符号位\(S\) | 阶码\(E=e+\text{Bias}_{64}\) | 尾数\(M\) |
下面先来看浮点数的生成方式。给定一个任意的无符号浮点数
则通过移位,理想情况下我们能得到
于是,理想情况下,我们将分别存储\(x_i.x_{i-1}x_{i-2}\cdots x_{-m+1}x_{-m}\)和\(i-n\)两个部分,不幸的是,这两个之中总可能会有一个超出能够表示的范围:
如果阶码的机器表示没有超出分配给它的位数,则这种数称为规格化的数,它的特点是尾数\(M\)有隐含的\(1\),偏置值为\(\text{Bais}_{32}=127\)或\(\text{Bais}_{64}=1023\),阶码值为\(e=E-\text{Bais}\),且阶码段既不是全\(1\)也不是\(0\)(因为全\(1\)和全\(0\)留给了后面的情况)。如果阶码的机器表示超出了分配给它的位数,则这种数称为非规格化数,它的特点是尾数\(M\)没有隐含的\(1\),阶码段为全\(1\)或全\(0\),当阶码段为全\(0\)时,阶码值\(e=1-\text{Bais}\)(之所以不是\(0-\text{Bais}\)是为了保证非规格化数和规格化数的平滑过渡),当阶码段为全\(1\)时,它的含义后面叙述。
下面的表格以\(8\)位比特为例说明了 IEEE 754 浮点数定义的转换方法和具体含义:
| 描述 | 位表示 | \(e\) | \(E\) | \(2^E\) | \(f\) | \(M\) | \(2^E\times M\) | \(V\) | 十进制 |
|---|---|---|---|---|---|---|---|---|---|
| \(0\) | \(0~0000~000\) | \(0\) | \(-6\) | \(\frac{1}{64}\) | \(\frac{0}{8}\) | \(\frac{0}{8}\) | \(\frac{0}{512}\) | \(0\) | \(0.0\) |
| 最小非规格化数 | \(0~0000~001\) | \(0\) | \(-6\) | \(\frac{1}{64}\) | \(\frac{1}{8}\) | \(\frac{1}{8}\) | \(\frac{1}{512}\) | \(\frac{1}{512}\) | \(0.001953\) |
| \(0~0000~010\) | \(0\) | \(-6\) | \(\frac{1}{64}\) | \(\frac{2}{8}\) | \(\frac{2}{8}\) | \(\frac{2}{512}\) | \(\frac{1}{256}\) | \(0.003906\) | |
| \(0~0000~011\) | \(0\) | \(-6\) | \(\frac{1}{64}\) | \(\frac{3}{8}\) | \(\frac{3}{8}\) | \(\frac{3}{512}\) | \(\frac{3}{512}\) | \(0.005859\) | |
| \(\cdots\) | |||||||||
| 最大非规格化数 | \(0~0000~111\) | \(0\) | \(-6\) | \(\frac{1}{64}\) | \(\frac{7}{8}\) | \(\frac{7}{8}\) | \(\frac{7}{512}\) | \(\frac{7}{512}\) | \(0.013672\) |
| 最小规格化数 | \(0~0001~000\) | \(1\) | \(-6\) | \(\frac{1}{64}\) | \(\frac{0}{8}\) | \(\frac{8}{8}\) | \(\frac{8}{512}\) | \(\frac{1}{64}\) | \(0.015625\) |
| \(0~0001~001\) | \(1\) | \(-6\) | \(\frac{1}{64}\) | \(\frac{1}{8}\) | \(\frac{9}{8}\) | \(\frac{9}{512}\) | \(\frac{9}{512}\) | \(0.017578\) | |
| \(\cdots\) | |||||||||
| \(0~0110~110\) | \(6\) | \(-1\) | \(\frac12\) | \(\frac{6}{8}\) | \(\frac{14}{8}\) | \(\frac{14}{16}\) | \(\frac78\) | \(0.875\) | |
| \(0~0110~111\) | \(6\) | \(-1\) | \(\frac12\) | \(\frac{7}{8}\) | \(\frac{15}{8}\) | \(\frac{15}{16}\) | \(\frac{15}{16}\) | \(0.9375\) | |
| \(0~0111~000\) | \(7\) | \(0\) | \(1\) | \(\frac{0}{8}\) | \(\frac{8}{8}\) | \(\frac{8}{8}\) | \(1\) | \(1.0\) | |
| \(0~0111~001\) | \(7\) | \(0\) | \(1\) | \(\frac{1}{8}\) | \(\frac{9}{8}\) | \(\frac{9}{8}\) | \(\frac{9}{8}\) | \(1.125\) | |
| \(0~0111~010\) | \(7\) | \(0\) | \(1\) | \(\frac{2}{8}\) | \(\frac{10}{8}\) | \(\frac{10}{8}\) | \(\frac54\) | \(1.25\) | |
| \(\cdots\) | |||||||||
| \(0~1110~110\) | \(14\) | \(7\) | \(128\) | \(\frac{6}{8}\) | \(\frac{14}{8}\) | \(\frac{1792}{8}\) | \(224\) | \(224.0\) | |
| 最大规格化数 | \(0~1110~111\) | \(14\) | \(7\) | \(128\) | \(\frac{7}{8}\) | \(\frac{15}{8}\) | \(\frac{1920}{8}\) | \(240\) | \(240.0\) |
| 正无穷大 | \(0~1111~000\) | - | - | - | - | - | - | \(+\infty\) | - |
| 负无穷大 | \(1~1111~000\) | - | - | - | - | - | - | \(-\infty\) | - |
| 非数 NaN | \(0/1~1110~\text{非}0\) | - | - | - | - | - | - | - | - |
可以看出,在不同区域,这样定义的浮点数表示的数字的精度是不同的。
字符与字符串采用 ASCII 码进行存储,它规定最高位为\(0\),余下的\(7\)位可以给出\(128\)个编码,包括大小写英文字母、数字符、运算符和标点符号以及一些控制码。常见的有:
值得一提的是,此处的数字符与上面提到的数据的存储不是一回事,上面的数据存储的基于二进制存储的(有权码),而此处的数字符是直接基于十进制存储的(无权码)。
有两种存储方式:大端法(big endian)和小端法(little endian)。大端法的最高有效字节在最前面,小端法的最低有效字节在最前面。假设变量int a=0x01234567,则大端法(第一个表格)和小端法(第二个表格)形式分别为(按字节编地址):
0x100 |
0x101 |
0x102 |
0x103 |
||
|---|---|---|---|---|---|
| \(\cdots\) | 01 |
23 |
45 |
67 |
\(\cdots\) |
0x100 |
0x101 |
0x102 |
0x103 |
||
|---|---|---|---|---|---|
| \(\cdots\) | 67 |
45 |
23 |
01 |
\(\cdots\) |
一切跨越多个字节的数据类型都需要考虑大小端法才能确定具体的存储顺序。其中每个字节内部存储的两个数字顺序是固定的,一个字节的\(8\)个比特位被切分为两个\(4\)比特位存储数据,将\(0\)到\(9\)这\(10\)个数字编码成\(4\)的方法有很多,比如 8421BCD 码、格雷码(及其他循环码)等等。
汉字在计算机中的存储分为三种情况:
对于定点数来说,加法和减法具有统一性,原因是因为
第一个式子可以分为四种情况来证明:
而第二个式子的证明如下:因为\([y]_{\text{补}}=[x+y]_{\text{补}}-[x]_{\text{补}}\)且\([-y]_{\text{补}}=[x+(-y)]_{\text{补}}-[x]_{\text{补}}=[x-y]_{\text{补}}-[x]_{\text{补}}\),将此两式相加得到
因此\(-[y]_{\text{补}}=[-y]_\text{补}\),所以\([x]_{\text{补}}-[y]_{\text{补}}=[x]_{\text{补}}+[-y]_{\text{补}}=[x-y]_{\text{补}}\)。
补码的加法是在取模运算之下所做的运算,也就是说,超出应有位数的部分应当被舍弃。下面通过两个例子来说明补码加减法的具体操作:
例 已知\(x_1=+(14)_{10}=+(1110)_2\),\(x_2=-(13)_{10}=-(1101)_2\),求\(x_1+x_2\)
首先求出\([x_1]_{\text{补}}=0~1110\)以及\([x_2]_{\text{补}}=1~0011\),相加得\(1~0~0001\),丢弃第一位,得\(0~0001\),即为\(+1\)。
例 已知\(x_1=+(13)_{10}=+(1101)_2\),\(x_2=+(6)_{10}=+(0110)_2\),求\(x_1-x_2\)
首先求出\([x_1]_{\text{补}}=0~1101\)以及\([-x_2]_{\text{补}}=1~1010\),相加得\(1~0~0111\),丢弃第一位,得\(0~0001\),即为\(+7\)。
当相加的两数都比较大的时候,有可能出现加和之后的结果大于该范围能表示的最大数的情况,该情况的直观体现为
需要注意的是,负数与正数相加(正数与负数相加)的不可能超出范围的。下面先看两个例子,再来分析溢出的原理和检测方法:
例 已知\(x_1=+(11)_{10}=+(1011)_2\),\(x_2=+(9)_{10}=+(1001)_2\),求\(x_1+x_2\)
首先求出\([x_1]_{\text{补}}=0~1011\)以及\([x_2]_{\text{补}}=0~1001\),相加得\(1~0100\),即为\(-12\),这显然是荒唐的。
例 已知\(x_1=-(13)_{10}=-(1101)_2\),\(x_2=-(11)_{10}=-(1011)_2\),求\(x_1+x_2\)
首先求出\([x_1]_{\text{补}}=1~0011\)以及\([x_2]_{\text{补}}=1~0101\),相加得\(1~0~1000\),丢弃第一位,得\(0~1000\),即为\(+8\),这显然是荒唐的。
溢出在区域上分为正溢(在正数范围发生的溢出)和负溢(在负数范围发生的溢出),在方向上分为上溢出(向大方向发生的溢出)和下溢出(向小方向发生的溢出),对于浮点数而言,一共有四种溢出情况:
| 不能表示 | 最大规格化数 | \(\cdots\) | 最小非规格化数 | 不能表示 | \(0\) | 不能表示 | 最小非规格化数 | \(\cdots\) | 最大规格化数 | 不能表示 |
|---|---|---|---|---|---|---|---|---|---|---|
| 负下溢 | 未溢出 | \(\cdots\) | 未溢出 | 负上溢 | \(0\) | 正下溢 | 未溢出 | \(\cdots\) | 未溢出 | 正上溢 |
而对于整型而言,正溢和上溢是相同的、负溢和下溢是相同的。
对整型而言,溢出的检测办法有两个。其一是变形补码法,例子为:
例 已知\(x_1=+(12)_{10}=+(1100)_2\),\(x_2=+(8)_{10}=+(1000)_2\),求\(x_1+x_2\)
首先求出\([x_1]_{\text{变补}}=00~1100\)以及\([x_2]_{\text{变补}}=00~1000\),相加得\(01~0100\),由于符号位为\(01\),故发生了正溢出。
例 已知\(x_1=-(12)_{10}=-(1100)_2\),\(x_2=-(8)_{10}=-(1000)_2\),求\(x_1+x_2\)
首先求出\([x_1]_{\text{变补}}=11~0100\)以及\([x_2]_{\text{变补}}=11~1000\),相加得\(1~10~1100\),丢弃第一位,得\(10~1100\),由于符号位为\(10\),故发生了负溢出。
那么我们可以知道,变形补码检测溢出的法则为:
该逻辑可用异或门实现,受此启发,我们得到溢出的另一种检测方法:单符号法
下面用两个例子说明这一点:
例 已知\(x_1=+(12)_{10}=+(1100)_2\),\(x_2=+(8)_{10}=+(1000)_2\),求\(x_1+x_2\)
首先求出\([x_1]_{\text{补}}=0~1100\)以及\([x_2]_{\text{补}}=0~1000\),相加得\(1~0100\),此时最高有效位产生了给符号位的进位\(1\),而符号位没有产生溢出的进位\(1\),因此属于正溢。
例 已知\(x_1=-(12)_{10}=-(1100)_2\),\(x_2=-(8)_{10}=-(1000)_2\),求\(x_1+x_2\)
首先求出\([x_1]_{\text{补}}=1~0100\)以及\([x_2]_{\text{补}}=1~1000\),相加得\(1~0~1100\),此时最高有效位没有产生给符号位的进位\(1\),而符号位却产生了溢出的进位\(1\),因此属于负溢。
定点加减法最简单的实现是基于一位全加器的连接,下图 (a) 为全加器的门电路(\(A_i\)和\(B_i\)为加数,\(C_i\)为来自上一位的进位),图 (b) 为行波进位的加法减法器:
下面来分析延迟。在门电路分析中,我们选定信号通过与门或者或门的时间为\(T\),且设经过异或门的时间为\(3T\),则对于单个全加器而言,产生加和\(S_i\)的延迟为\(3T+3T=6T\)、产生进位\(C_{i+1}\)的延迟为\(3T+T+T=5T\)。对于较为复杂的门电路来说,要计算它的延迟就是要找到其中最长的时间链,对于上图的行波进位的加法减法器来说,最长的时间链就是从\(A_0\)和\(B_0\)输入经过\(n\)次进位最终产生溢出标志的链路,该链路所花费的时间为
可以看出,这种方式的加法在进位上花费了大量的时间,下面来看先行加法的设计思路,该方法加速了加法过程。令\(P_i=A_i\oplus B_i\)、\(G_i=A_iB_i\),则进位为\(C_{i+1}=G_i+P_iC_i\),那么对于连续的加法而言:
这样一直推下去,理论上说,任意位数的加法中的任意一个进位都可以根据直接输入的数值计算出来(而不必依赖进位),根据多方面的考虑,常常以四位为一组,组内进行超前加法(先行加法、并行进位),而组之间使用传统的进位加法(串行进位、即\(C_5\)依赖于进位而不是直接计算得到),每个这样的单元称为四位先行进位部件(Carry Look Ahead or CLA),每个这样的单元延迟为\(2T\)(因为经过了一次多重与门和一次多重或门,且不考虑\(P_i\)和\(G_i\)的生成延迟)。对于两个\(16\)位数的不考虑溢出检测的加法来说,使用四个这样的单元就能完成加法过程,门电路如下图所示:
左侧的虚线框的功能是求出所有的\(P_i\)与\(G_i\),中间虚线框的功能是进行串行进位操作,右侧的虚线框的功能是根据\(A_i\)、\(B_i\)和\(C_i\)求出加和结果\(S_i\)。该结构中最长的时间链为从\(A_0\)和\(B_0\)进去、生成\(P_0\)、再经过先行进位与传统进位到达最上面的 CLA、再经过求和操作得到最终的结果,延迟为
注意,最后生成\(S_{15}\)的时候只经过了一个异或门,因为\(S_{15}=A_{15}\oplus B_{15}\oplus C_{15}\)中的第一个异或门已经在更早的时候经过了。如果采用完全串行进位,则延迟为
可以看到延迟有较大的差异。这里加法不考虑溢出检测,故不论是经由 CLA 实现的加法还是传统的串行加法,都没有考虑溢出检测所花费的时间(因此传统串行加法中的最后一次进位没有必要)。在 CLA 实现的加法中如果考虑溢出检测,因为溢出检测经过一个异或门(即\(C_{16}\oplus C_{15}\))的延迟也为\(3T\),因此总延迟不变。
由于原码与真值极为相似(只差一个符号),而乘积的符号又可以通过两数符号的异或得到,因此,对于原码的乘法而言,只需要忽略符号位进行典型的二进制乘法,最后在结果中单独添加一个符号位即可,下面通过一个例子来说明:
例 已知\(x=-0.1110\),\(y=-0.1101\),求\([x\cdot y]_{\text{原}}\)
首先求出\([x]_{\text{原}}=1.1110\)以及\([y]_{\text{原}}=1.1101\),然后取出不含符号位的\(1110\)和\(1101\)两部分相乘,得到\(10110110\),然后在前面加上符号位,得到\([x\cdot y]_{\text{原}}=0.10110110\)。
下面讨论不带符号(即无符号数,原码中不含符号位的部分的乘法也符合此情况)的阵列乘法器,不带符号的阵列乘法器的门电路为:
被加数为一系列的\(a_ib_j\),接下来的阵列乘法器将对这些被加数进行对应的加法,阵列乘法器为:
以上是五位二进制数乘以五位二进制数的阵列器,斜线表示来自上一级的进位输入,最后输出的结果为\(p_9p_8\cdots p_1p_0\)。对于这个器件来说,最长的链路为从\(a_4b_0\)与\(a_3b_1\)的全加器、到\(a_2b_2\)的全加器、到\(a_1b_3\)的全加器、到\(a_0b_4\)的全加器、到最后一行最右侧的全加器、再经过\(3\)次进位到最后一行最左侧的全加器、最后输出\(p_8\)的链路,所以延迟为
如果我们输入的是两个原码,那么根据上面的不带符号的阵列乘法器,我们已经计算得到了不含符号位的乘法结果(比如输入\(-3\)和\(5\)时,得到的是\(15\)),如果要使得到的结果也是补码表示,那么在所得结果之前加上两个乘数的符号位的异或即可。
对于补码形式的数而言,它的乘法规则不再显然,通常我们先将这两个数转换为原码形式(通过后面所述的求补器实现),再按照原码的定点乘法规则(符号位单独考虑)进行乘法,在得到的乘法结果前面加上单独考虑的符号位,得到了原码表示的结果。如果想让最后的结果以补码形式表示,则只需要在最后使其通过求补器即可。下面通过两个例子说明该思路的具体操作过程:
例 已知\(x=+(13)_{10}=+(1101)_2\),\(y=+(11)_{10}=+(1011)_2\),求\(x\cdot y\)
首先求出\([x]_{\text{原}}=0~1101\)以及\([y]_{\text{原}}=0~1011\),然后将不含符号的\(1101\)和\(1011\)相乘,得\(1000~1111\),两个符号位单独异或,得到积的符号位为\(0\oplus0=0\),则结果为\([x\cdot y]_{\text{原}}=0~1000~1111\),即\(x\cdot y=+(1000~1111)=143\)。
例 已知\(x=+(3)_{10}=+0011\),\(y=-(11)_{10}=-(1011)_2\),求\(x\cdot y\)
首先求出\([x]_{\text{原}}=0~0011\)以及\([y]_{\text{原}}=1~1011\),然后将不含符号的\(0011\)和\(1011\)相乘,得\(100001\),两个符号位单独异或,得到积的符号位为\(0\oplus1=1\),则结果为\([x\cdot y]_{\text{原}}=1~100001\),即为\(x\cdot y=-(100001)=-33\)。
上面这种将补码转换为原码的方法,在思路上很简便,将补码的情况转变成了我们在2.4节中讨论过的情况。下面介绍直接使用补码进行乘法的思路,该思路不需要转换为原码,一定程度上减少了出错的可能性。
补码不能直接参与乘法的原因是:符号位的含义与后面的一般数码没有统一性,参考资料中唐朔飞著作中提到了当两个乘数的补码形式的符号任意时的Booth算法,此处略去不谈。
之前现在我们已经讨论了原码的乘法器实现,如果试图计算的是两个补码的乘法结果,那么对于输入的补码而言,根据2.6节的内容,先将这两个数转换为原码形式(通过后面所述的求补器实现),再按照原码的定点乘法规则(符号位单独考虑)进行乘法,在得到的乘法结果前面加上单独考虑的符号位,得到了原码表示的结果。如果想让最后的结果以补码形式表示,则只需要在最后使其通过求补器即可,门电路如下所示:
其中求补器的门电路为:
当\(E=1\)时进行求补运算,当\(E=0\)时输入和输出一样。该器件实现的思路为:从数的最右端\(a_0\)开始,由右向左,直到找出第一个\(1\),例如\(a_i=1(0\leq i\leq n)\)。这样,\(a_i\)以右的每一个输入位,包括\(a_i\)自己,都保持不变,而\(a_i\)以左的每一个输入位都求反。鉴于此,横向链式线路中的第\(i\)扫描级的输出\(C_i\)为\(1\)的条件是:第\(i\)级的输入位\(a_i=1\),或者第\(i\)级链式输入(来自右起前\(i–1\)级的链式输出)\(C_i–1=1\)。另外,最右端的起始链式输入\(C_{–1}\)必须永远置成\(0\)。如果我们有\(n+1=5\)位的输入,由于符号位单独考虑,则该求补器中最长的链路为:从\(C_{-1}\)开始、经过\((n+1)-1\)次进位、再经过一个与门和一个异或门输出\(a_3^*\)的链路,因此延迟为
注意,和前面的假设不同的是,这里假设与门与或门延迟为\(2T\),异或门延迟为\(3T\)。
最后指出,由于补码的唯一性,我们既可以对补码求补得到原码(符号位单独考虑),也可以对补码求补得到原码(符号位单独考虑)。
先来讨论原码的除法。由于在二进制除法中商数只可能是\(1\),故余数减去一倍的除数只有两种情况:够减(减法结果大于零,此时令商为\(1\))与不够减(减法结果小于零,此时令商为\(0\)),在实际应用中,有两种具体的思路:
为了避免不够减时返回去重新计算,我们采用加减交替的思路,它的原理由下式保证
其中\(r_{i-1}\)是当前的余数(即将作为被减数,减数为除数\(y\)),\(r_i'=2r_{i-1}-y\)可能是新的余数(如果该数大于零则是,小于零则不是)(大于零则令商为\(1\),小于零则令商为\(0\)),\(r_i\)是真的新的余数(但我们只关心最后的结果商,而不关心这种中途生成的余数),从上式可以看出,下一级的可能余数\(r_{i+1}'\)的生成方式取决于当前的可能余数\(r_i'\)(减去除数\(y\)或加上除数\(y\)),如果\(r_{i+1}'>0\)则下一位商置为\(1\),如果\(r_{i+1}'<0\)则下一位商置为\(0\),如此不断进行下去,直到出现某个\(r_j=0\)或者到达最大精度。
上面讨论的原码除法,适用于任何原码之间的除法(正数与正数、正数与负数、负数与正数、负数与负数,中间两种的符号位单独处理),虽然称为原码乘法,但运算中涉及到的加减法仍然是补码的,所谓“原码”仅是指参与除法的两个数需要先取绝对值才能才与运算,至于商数的符号位则单独确定,这和原码乘法是类似的,但不同之处在于,原码乘法中不涉及到减法而原码除法涉及到减法。
我们在此只讨论了原码(即无符号数,原码中不含符号位的部分的乘法也符合此情况)的除法原理,有符号数(比如补码)的情况后面再谈。后面的例子说明这一点。下面通过两个例子说明原码定点除法的实现过程:
例 已知\(x=+(9)_{10}=+(1001)_2\),\(y=+(11)_{10}=+(1011)_2\),求\(x\div y\)
首先求出\([x]_{\text{原}}=0~1001\)以及\([y]_{\text{原}}=0~1011\),然后取绝对值(即所谓的无符号数)\(0~1001\)和\(0~1011\)依次进行下面的操作:
| \(i\) | 被除数 | 除数 | \(2r_{i-1}'\) | \(r_i'=2r_{i-1}'\pm y\) | 商 |
|---|---|---|---|---|---|
| \(1\) | \(0~1001\) | \(0~1011\) | 被除数 | \(\text{被除数}-0~1011=0~1001+1~0101=1~1110<0\) | \(0\) |
| \(2\) | \(0~1001\) | \(0~1011\) | \(1~1100\) | \(1~1100+0~1011=1~0~0111\),丢弃第一位得\(0~0111>0\) | \(1\) |
| \(3\) | \(0~1001\) | \(0~1011\) | \(0~1110\) | \(0~1110-0~1011=1~0~0011\),丢弃第一位得\(0~0011>0\) | \(1\) |
| \(4\) | \(0~1001\) | \(0~1011\) | \(0~0110\) | \(0~0110-0~1011=1~1011<0\) | \(0\) |
| \(5\) | \(0~1001\) | \(0~1011\) | \(1~0110\) | \(1~0110+0~1011=1~0~0001\),丢弃第一位得\(0~0001>0\) | \(1\) |
| \(\cdots\) | \(0~1001\) | \(0~1011\) | \(\cdots\) | \(\cdots\) | \(\cdots\) |
然后对符号位单独异或\(0\oplus0=0\),因此\([\text{商}]_{\text{原}}=0.1101\)(第一位是符号位)。
例 已知\(x=+(9)_{10}=+(1001)_2\),\(y=-(11)_{10}=-(1011)_2\),求\(x\div y\)
首先求出\([x]_{\text{原}}=0~1001\)以及\([y]_{\text{原}}=1~1011\),然后取绝对值(即所谓的无符号数)\(a=0~1001\)和\(b=0~1011\)依次进行下面的操作:
| \(i\) | 被除数\(a\) | 除数\(b\) | \(2r_{i-1}'\) | \(r_i'=2r_{i-1}'\pm y\) | 商 |
|---|---|---|---|---|---|
| \(1\) | \(0~1001\) | \(0~1011\) | 被除数 | \(\text{被除数}-0~1011=0~1001+1~0101=1~1110<0\) | \(0\) |
| \(2\) | \(0~1001\) | \(0~1011\) | \(1~1100\) | \(1~1100+0~1011=1~0~0111\),丢弃第一位得\(0~0111>0\) | \(1\) |
| \(3\) | \(0~1001\) | \(0~1011\) | \(0~1110\) | \(0~1110-0~1011=1~0~0011\),丢弃第一位得\(0~0011>0\) | \(1\) |
| \(4\) | \(0~1001\) | \(0~1011\) | \(0~0110\) | \(0~0110-0~1011=1~1011<0\) | \(0\) |
| \(5\) | \(0~1001\) | \(0~1011\) | \(1~0110\) | \(1~0110+0~1011=1~0~0001\),丢弃第一位得\(0~0001>0\) | \(1\) |
| \(\cdots\) | \(0~1001\) | \(0~1011\) | \(\cdots\) | \(\cdots\) | \(\cdots\) |
然后对符号位单独异或\(0\oplus1=1\),因此\([\text{商}]_{\text{原}}=1.1101\)(第一位是符号位)。
下面来看纠正余数的问题。采用加减交替法的原码除法的一个小缺陷就是最后得到的余数可能不是真正的余数(但最后得到的商数一定是真的商数)。原码除法的余数纠正并不是明晰的,因为它涉及到原码和补码的转换,我们将在后面补码除法除法中详细说明余数纠正的一般规则。
从上面的内容,我们不难发现,原码的定点除法具有下面的特征:
根据上面的特征,我们可以设计出可控加法减法(CAS)单元如下:
在计算的一开始,\(A_i\)为被除数的某一位,在后面的过程中,\(A_i\)为\(2r_i'\)的某一位,\(P=0\)则进行加法运算,\(P=1\)则进行减法运算,\(B_i\)为除数的某一位(将保持不变向下传递),\(C_i\)为来自上一级的进位(进行加法时\(C_{-1}=0\),进行减法时\(C_{-1}=1\)),\(S_i\)为加和结果,\(C_{i+1}\)为进位结果。下图展示了如何将这些单元组合起来构成\(4\)位除\(4\)位的阵列除法器:
(最后得到的是不含符号位的商)。其中,沿着斜线输入的\(0y_3y_2y_1\)为除数,沿着竖线输入的\(0x_6x_5x_4x_3x_2x_1\)为被除数。对于第一行而言,第一次执行的操作为减法,故输入\(P=1\)(且第一行最右侧输入的\(C_{-1}=P=1\)),第一行最左侧的进位输出用于判断可能余数的正负(由此决定下一次进行加法还是减法),如果进位为\(0\)则说明可能余数为负数(此时令商为\(0\)且下一次进行加法),如果进位为\(1\)则说明可能余数为正数(此时令商为\(1\)且下一次进行减法)。从例\(11\)也可以看出,正数总是通过丢弃最高位才得到的,这实际上由更深入的原理来保证。在严格的\(4\)位除\(4\)位的除法当中,被除数\(0x_6x_5x_4x_3x_2x_1=0x_6x_5x_4000\),在除法向下推进的过程中,上一次得到的可能余数的最高位不再参与运算,并在最低位引入\(x_3\)或\(x_2\)或\(x_1\),这满足了可能余数\(r_i'\)在参与下一级运算之前需要左移一位的要求。
现在来分析延迟。在上述所示的\(4\)位除\(4\)位的阵列除法器中,我们记真正有效的位数\(3\)为\(n\),则被除数有效部分为\(2n\),除数有效部分为\(n\),阵列除法器一共有\((n+1)^2\)个 CAS 单元。该阵列除法器中最长的链路为:从\(P=1\)输入开始,经过每一行的每一次进位再进入下一行、直到最后生成最后一个商\(p_1\)的时间,因此延迟为
现在略去具体的推导(详见参考资料中唐朔飞著作),直接给出补码除法的一般规则:
| \([x]_{\text{补}}\)与\([y]_{\text{补}}\) | 商 | \([R]_{\text{补}}\)与\([y]_{\text{补}}\) | 上商 | 下一步 |
|---|---|---|---|---|
| 同号 | 正数 | 同号,表示够减 | \(1\) | \(+[-y]_{\text{补}}\) |
| 同号 | 正数 | 异号,表示不够减 | \(0\) | \(+[y]_{\text{补}}\) |
| 异号 | 负数 | 异号,表示够减 | \(0\) | \(+[y]_{\text{补}}\) |
| 异号 | 负数 | 同号,表示不够减 | \(1\) | \(+[-y]_{\text{补}}\) |
下面通过两个例子加以说明:
例 已知\(x=-1001\),\(y=+1101\),求\(x\div y\)
首先求出\([x]_{\text{补}}=1~0111\)以及\([y]_{\text{补}}=0~1101\),计算过程如下:
| \(i\) | \([x]_{\text{补}}\) | \([y]_{\text{补}}\) | \(2r_{i-1}'\) | \(r_i'=2r_{i-1}'\pm y\) | 商 |
|---|---|---|---|---|---|
| \(1\) | \(1~0111\) | \(0~1101\) | 被除数 | \(1~0111+0~1101=1~0~0100\),丢弃第一位得\(0~0100\)与\([y]_{\text{补}}\)同号 | \(1\) |
| \(2\) | \(1~0111\) | \(0~1101\) | \(0~1000\) | \(0~1000-0~1101=1~1011\),与\([y]_{\text{补}}\)异号 | \(0\) |
| \(3\) | \(1~0111\) | \(0~1101\) | \(1~0110\) | \(1~0110+0~1101=1~0~0011\),丢弃第一位得\(0~0011\)与\([y]_{\text{补}}\)同号 | \(1\) |
| \(4\) | \(1~0111\) | \(0~1101\) | \(0~0110\) | \(0~0110-0~1101=1~1001\),与\([y]_{\text{补}}\)异号 | \(0\) |
| \(\cdots\) | \(1~0111\) | \(0~1101\) | \(\cdots\) | \(\cdots\) | \(\cdots\) |
因此\([x\div y]_{\text{补}}=1.010\),故\(x\div y=-0.110\)。
例 已知\(x=+1001\),\(y=+1101\),求\(x\div y\)
首先求出\([x]_{\text{补}}=0~1001\)以及\([y]_{\text{补}}=0~1101\),计算过程如下:
| \(i\) | \([x]_{\text{补}}\) | \([y]_{\text{补}}\) | \(2r_{i-1}'\) | \(r_i'=2r_{i-1}'\pm y\) | 商 |
|---|---|---|---|---|---|
| \(1\) | \(0~1001\) | \(0~1101\) | 被除数 | \(0~1001-0~1101=1~1100\),与\([y]_{\text{补}}\)异号 | \(0\) |
| \(2\) | \(0~1001\) | \(0~1101\) | \(1~1000\) | \(1~1000+0~1101=1~0~0101\),丢弃第一位得\(0~0101\)与\([y]_{\text{补}}\)同号 | \(1\) |
| \(3\) | \(0~1001\) | \(0~1101\) | \(0~1010\) | \(0~1010-0~1101=1~1101\),与\([y]_{\text{补}}\)异号 | \(0\) |
| \(4\) | \(0~1001\) | \(0~1101\) | \(1~1010\) | \(1~1010+0~1101=1~0~0111\),丢弃第一位得\(0~0111\)与\([y]_{\text{补}}\)同号 | \(1\) |
| \(\cdots\) | \(0~1001\) | \(0~1101\) | \(\cdots\) | \(\cdots\) | \(\cdots\) |
因此\([x\div y]_{\text{补}}=0.101\),故\(x\div y=+0.101\)。
下面来看余数的纠正:
来看例子:
结构和原码的除法门电路一样,只不过涵盖了符号位,且检测上商的时候不仅需要可能余数\([R]_{\text{补}}\)的符号位、而且需要除数\([y]_{\text{补}}\),这两者同号则上商\(1\),否则上商\(0\)。另外,原码除法必定是正数除以正数,所以第一步一定进行的是减法,但在补码除法中如果被除数和除数同号则第一步进行减法、否则进行加法,所以首先输入的\(P\)可能是\(1\)(进行减法)、也可能是\(0\)(进行加法)。
完成浮点加减法的大致过程分为四步:
接下来结合下图说明具体过程:
上图显示的是\(x\pm y\)的操作过程。在对阶操作中,总是小阶向大阶对齐,比如\(x=1.000_2\times2^{-1}\),\(y=-1.110_2\times2^{-2}\),则\(y\)应该转成\(y=-0.111_2\times2^{-1}\)。注意,在此对阶过程中可能出现尾数丢失的问题,因此要进行所谓的舍入处理(后面叙述)。在对阶完成之后,进行加减法,然后对尾数进行规格化处理,此时在规格化时也涉及到了舍入处理,现在来讨论这个处理的具体内容。舍入处理发生在对阶和向右规格化时,这样,被右移的尾数的低位部分就会被丢掉,从而造成一定误差,所以进行舍入处理,常见的舍入处理有下面几种:
同时,在最后对结果进行规格化的过程中,阶码可能会溢出,常见的阶码溢出和处理手段如下所示:
当然,在尾数进行加减法运算的时候,也可能会溢出,但是这种溢出可能是不致命的,常见的尾数溢出种类和处理手段如下所示:
浮点乘数法的计算过程比浮点加减法的计算过程从概念上来说简单许多,大体上分为六个步骤:
上面的步骤暗示出此处的尾数乘法实际上是按照原码进行的(因为符号位单独处理),也就是用到了2.5节中叙述的原理。当然,在此过程中也要时刻注意是否有溢出问题。浮点乘除法的整个流程如下图所示:

请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
我正在使用Ruby,我正在与一个网络端点通信,该端点在发送消息本身之前需要格式化“header”。header中的第一个字段必须是消息长度,它被定义为网络字节顺序中的2二进制字节消息长度。比如我的消息长度是1024。如何将1024表示为二进制双字节? 最佳答案 Ruby(以及Perl和Python等)中字节整理的标准工具是pack和unpack。ruby的packisinArray.您的长度应该是两个字节长,并且按网络字节顺序排列,这听起来像是n格式说明符的工作:n|Integer|16-bitunsigned,network(bi
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复