计算机组成原理(一) —— 浮点数表示与运算

最近可能会更几篇计算机组成原理相关的文章,主要秋招面试的时候遇到,以及要做的项目里有涉及到的内容,不会特别细致吧,主要还是目前自己的学习记录。



浮点数表示

这里的浮点数表示是基于 IEEE754 标准,IEEE 浮点数标准用 V = ( − 1 ) s × M × 2 E V = (-1)^s \times M \times 2^E V=(1)s×M×2E 的形式来表示一个数:

  • 符号(sign): s s s 决定这数是负数( s = 1 s=1 s=1)还是正数( s = 0 s=0 s=0)
  • 阶码(exponent): E E E 浮点数表示是指数,作用是对浮点数甲醛,权重是 2 的 E E E 次幂(可能是负数)
    k k k 位的阶码字段 e x p = e k − 1 . . . e 1 e 0 exp=e_{k-1}...e_1e_0 exp=ek1...e1e0 编码阶码 E E E 这里还和 B i a s Bias Bias 有关,放在后面浮点数类型分类细说
  • 尾数(significand): M M M 是一个二进制小数, n n n 位小数字段 f r a c = f n − 1 . . . f 1 f 0 frac=f_{n-1}...f_1f_0 frac=fn1...f1f0 编码尾数 M,这里编码结果依赖阶码是否为 0,具体也放在后面类型分类细说。

C语言的单精度浮点格式中(32位), s s s e x p exp exp f r a c frac frac 字段分别为 1 位、 k = 8 k=8 k=8 位和 n = 23 n=23 n=23 位,得到一个 32 位的表示,尾数部分 23 位,换算成十进制就是 2 23 = 8388608 2^{23} = 8388608 223=8388608,所以十进制精度只有 6~7 位。
C语言的双精度浮点格式中(64位), s s s e x p exp exp f r a c frac frac 字段分别为 1 位、 k = 11 k=11 k=11 位和 n = 52 n=52 n=52 位,得到一个 64 位的表示,尾数部分 52 位,换算成十进制就是 2 52 = 4503599627370496 2^{52} = 4503599627370496 252=4503599627370496,所以十进制精度只有 15~16 位。


类型分类

规格化的值


这是最普遍的情况。当 e x p exp exp 的位模式既不全为 0(数值 0),也不全为 1(单精度数值为 255,双精度数值为 2047)时,都属于这类情况。在这种情况中,阶码字段被解释为以偏置(biased)形式表示的有符号整数。

也就是说,阶码的值是 E = e − B i a s E = e-Bias E=eBias,其中 e e e 是无符号数,其位表示为 e k − 1 . . . e 1 e 0 e_{k-1}...e_1e_0 ek1...e1e0,而 B i a s Bias Bias 是一个等于 2 k − 1 − 1 2^{k-1}-1 2k11(单精度是 127,双精度是 1023)的偏置值。由此产生指数的取值范围,对于单精度是 − 126   + 127 -126~+127 126 +127,而对于双精度是 − 1022   + 1023 -1022~+1023 1022 +1023

小数字段 f r a c frac frac 被解释为描述小数值 f f f,其中 0 ≤ f < 1 0 \le f \lt 1 0f<1,其二进制表示为 0. f n − 1 . . . f 1 f 0 0.f_{n-1}...f_1f_0 0.fn1...f1f0,也就是二进制小数点在最高有效位的左边。尾数定义为 M = 1 + f M=1+f M=1+f。这种方式可以叫做隐含的以 1 开头的(implied leading 1)表示,因为我们可以把 M M M 看成一个二进制表达式为 1. f n − 1 f n − 2 . . . f 0 1.f_{n-1}f_{n-2}...f_0 1.fn1fn2...f0 的数字。这种方式可以让我们获得一个额外的精度位,因为我们总是可以通过调整阶码的方式,让尾数以 1 开头,那么就不需要额外再用一位显式地表示它。

非规格化的值

当阶码域为全 0 时,所表示的数是非规格化形式。在这种情况下,阶码值是 E = 1 − B i a s E=1-Bias E=1Bias,而尾数的值是 M = f M=f M=f 也就是小数字段的值,不包含隐含的开头的 1。

这里阶码值为 1-Bias 而不是简单的 -Bias 可以提供一种从非规格化值平滑转换到规格化值的方法。

非规格化数有两个用途:

  1. 提供一种表示数值 0 的方法,上面的规格化数,必须总是使 M ≥ 1 M \ge 1 M1,因此我们就不能表示 0。+0.0 的浮点表示的位模式为全 0:符号位是 0,阶码字段全为 0(表明是一个非规格化值),而小数域也全为 0,这就得到 M = f = 0 M=f=0 M=f=0。当符号位为 1,而其它域全为 0 时,我们得到值 -0.0。根据 IEEE 的浮点格式,值 +0.0 和 -0.0 在某些方面被认为是不同的,比如 1/+0.0 会得到正无穷大(后面会提到),1/-0.0 会得到负无穷大。

  2. 表示那些非常接近于 0.0 的数。它们提供了一种属性,称为逐渐溢出(gradual underflow),其中,可能的数值分布均匀地接近于 0.0。这里顺便提一下,浮点数可表示的数并不是均匀分布的,而是越靠近原点处越稠密

特殊值

当阶码全为 1 的时候为特殊值。当小数域全为 0 时,得到的值表示无穷,当 s = 0 s=0 s=0 时是 + ∞ +\infty +,或者当 s = 1 s=1 s=1 时是 − ∞ -\infty 。当我们把两个非常大的数相乘,或者除以零时,无穷能够表示溢出的结果。

当小数域为非零时,结果值被称为 “NaN”,即 Not a Number 的缩写。一些运算的结果不能是实数或无穷,就会返回这样的 NaN 值,比如当计算 − 1 \sqrt{-1} 1 ∞ − ∞ \infty - \infty 时。在某些应用中,表示未初始化的数据时,它们也很有用处。


数字示例

下面表格展示 8 位浮点格式的非负值示例( k = 4 k=4 k=4 的阶码位和 n = 3 n=3 n=3 的小数位。偏置量是 7)

从上表就可以看出非规格化值与规格化值之间的平滑过渡。


浮点数转换

下面主要讲解是浮点数十进制和二进制之间的相互转换

十进制转换为二进制

可以分为三步:

  1. 整数部分转换
  2. 小数部分转换
  3. 合并结果

用一个例子来说明,比如说 (float)1.6
(1) 整数部分转换,使用除 2 取余法即可。1) 1 % 2 = 1; 2) 1 / 2 = 0 over,得到整数部分的二进制数为 1。
(2) 小数部分转换,使用乘 2 取整法:
--------------- 0.6 × 2 = 1.2 0.6 \times 2 =1.2 0.6×2=1.2 => 1
--------------- 0.2 × 2 = 0.4 0.2 \times 2 = 0.4 0.2×2=0.4 => 0
--------------- 0.4 × 2 = 0.8 0.4 \times 2 = 0.8 0.4×2=0.8 => 0
--------------- 0.8 × 2 = 1.6 0.8 \times 2 = 1.6 0.8×2=1.6 = 1

最终得到的小数部分的表示为 0.1001 1001 1001 1001 1001 100
(3) 合并结果 1.1001 1001 1001 1001 1001 100 转换为浮点数标准为 1.1...0 × 2 0 1.1...0 \times 2^{0} 1.1...0×20 E = 01111111 E = 0111 1111 E=01111111

二进制转十进制

其实就是一个逆过程,但需要注意的是,十进制转二进制后,我们能保存的位数是有限的,所以会存在精度丢失,比如上面的 1.6 转换时无限循环的,那么从十进制转换到二进制,再从二进制转换回十进制本身是不可逆的。


浮点运算

浮点运算主要涉及到的就是加减法和乘除法,以及在实际应用中要注意的问题。

加减法

浮点数的加减预算一般由以下五个步骤完成

对阶

对阶就是将两个进行运算的浮点数的阶码对齐。目的是为使两个浮点数的尾数能够进行加减运算。

具体方法是求出两个浮点数阶码的差,即 △ E = E x − E y \triangle E=E_x - E_y E=ExEy,将小阶码加上 △ E \triangle E E,使之与大阶码相等,同时将小阶码对应的浮点数的尾数右移相应位数,以保证该浮点数的值不变。

需要注意的几个地方:

  1. 对阶原则是小阶对大阶,之所以这样做是因为若大阶对小阶,则尾数的数值部分的高位需移出,而小阶对大阶移出的是尾数的数值部分的低位,这样损失的精度更小。
  2. △ E = 0 \triangle E=0 E=0,说明两浮点数的阶码已经相同,无需再做对阶操作了。
  3. 采用补码表示的尾数右移时,符号位保持不变。
  4. 由于尾数右移时是将最低位移出,会损失一定的精度,为减少误差,可以保留若干移出的位,供以后舍入处理用。

尾数运算

尾数运算就是完成对阶后的尾数相加减。这里和定点数加减运算一样。

结果规格化

为保证浮点数表示的唯一性,浮点数在机器中都是以规格化形式存储的。就是尾数必须是 1.M 的形式。在进行了上面的尾数相加/减运算后,尾数可能是非规格形式,因此需要进行规格化操作。

根据不同情况,规格化操作分为左规操作和右规操作:

  • 左规操作:用于类似 M = 0.0101 M=0. 0101 M=0.0101的情况,将尾数左移,同时阶码减去移动次数
  • 右规操作:用于类似 M = 10. x x x M=10.xxx M=10.xxx M = 11. x x x M=11.xxx M=11.xxx的形式,将尾数右移,同时阶码加上移动次数,需要注意的是,加减操作,最多会需要右移 1 次

舍入处理

由前面的步骤可知,浮点运算在对阶或右规时,尾数需要右移,被右移出去的位会被丢掉,从而造成运算结果精度的损失。为了减少这种精度损失,可以将一定位数的移出位先保留起来,称为保护位,在规格化后用于舍入处理。

IEEE754 标准列出了四种可选的舍入处理方法:

  1. 就近舍入 (round to nearest) 这是标准列出的默认舍入方式,和四舍五入类似,需要注意就是如果是中间值,会向偶数舍入。例如,对于32位单精度浮点数来说,若超出可保存的23位的多余位大于等于100…01,则多余位的值超过了最低可表示位值的一半,这种情况下,舍入的方法是在尾数的最低有效位上加1;若多余位小于等于011…11,则直接舍去;若多余位为100…00,此时再判断尾数的最低有效位的值,若为0则直接舍去,若为1则再加1。
  2. + ∞ +\infty +舍入,正数多余位不全为0,则向尾数最低有效位进 1;对负数来说,简单舍去。
  3. − ∞ -\infty 舍入,负数多余位不全为0,向尾数最低有效位进 1;对正数来说,简单舍去。
  4. 向 0 舍入,直接截断舍去。

溢出判断

浮点数的溢出是看最后阶码的值是否产生溢出判断的:

  • 若阶码的值超过了阶码所能表示的最大正数,则为上溢,若浮点数为正数为正上溢,记为 + ∞ +\infty +,若浮点数为负数,则为负上溢,记为 − ∞ -\infty
  • 若阶码的值超过了阶码所能表示的最小负数,则为下溢,若浮点数为正数,则为正下溢,记为 + 0 +0 +0,若浮点数为负数,则为负下溢,记为 − 0 -0 0

具体的计算实例可以看这篇博客的内容,后续有时间补个例子。。。

乘除法

与加减法类似,除了不需要对阶了:

  1. 阶码相加/减
  2. 尾数相乘/除
  3. 结果规格化
  4. 舍入处理
  5. 溢出判断

后面有时间补个例子。。。


浮点数使用事宜

  1. 防止大数吃小数,因为在浮点数加减的过程中,需要对阶,相应会将小数的尾数部分右移,如果大数很大, 那么就会把小数的精度给吃掉。
  2. 浮点数加法不满足结合律,比如 (3.14+1e10)-1e10得到0.0,而3.14+(1e10-1e10值为3.14,本质上就是上面的大数吃小数问题。
  3. 浮点数乘法也不满足结合律,比如 (1e20*1e20)*1e-20求值为+100,而1e20*(1e20*1e-20)将得出1e20,本质上浮点数表示是有上限。
  4. 浮点乘法在加法上不具备分配性,比如 1e20*(1e20-1e20)求值为 0.0,而 1e20*1e20-1e20*1e20会得出 NaN。

参考资料

深入理解计算机系统 —— 2.4 浮点数
浮点数的运算步骤
真有小伙伴不知道浮点数如何转二进制吗?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值