IEEE浮点数

2.4 浮点数

浮点数在计算机中的表示和现实中科学计数法的表示类似,对于形如 V = x × 2 y V=x\times 2^y V=x×2y 的有理数进行编码。

直到20世纪80年代,很多计算机厂家都设计了自己的浮点数表示规则,导致了一些混乱,直到IEEE 754标准的推出,才有了统一的标准。我们会在本章介绍IEEE的这个浮点数标准。

2.4.1 二进制小数

对于整数而言,我们知道每一位的权重实际上是2的整数次幂(正整数次幂),而对于小数而言,实际上就变成了负整数次幂。

对于一个 b m b m − 1 . . . b 0 . b − 1 b − 2 . . . b − n b_mb_{m-1}...b_0.b_{-1}b_{-2}...b_{-n} bmbm1...b0.b1b2...bn的小数,小数点右边的各位的权重分别为-1,-2,-3等等一直往下。而整个浮点数的值就是:
b = Σ i = − n m b i × 2 i b=\Sigma_{i=-n}^{m}b_i\times2^i b=Σi=nmbi×2i
事实上,这样进行表示是无法精确表示很多小数的,只能做到近似,而提高位数可以提高近似的程度。

上面的这种表示方法,叫做定点表示法,也就是小数点的位置固定,然后把所有的位进行存储。比如我们存储1011 1001 0111 0101,那么可能是1011 1001.0111 0101

可以发现,如果使用定点表示法,那么能够表示的范围是很有限的。因为整数部分只能占部分位数,小数也是如此,可以类比一下,如果直接写出一个十进制小数,比如1234.5678,使用了8位数,而如果我们用科学计数法,比如 1 × 1 0 10 1\times10^{10} 1×1010,我们只需要存一个底数,和一个指数,这样就能表示一个很大的数,范围就变大了很多倍。于是,我们接下来介绍IEEE的浮点表示。

2.4.2 IEEE浮点表示

IEEE使用 V = ( − 1 ) s × M × 2 E V=(-1)^s\times M\times2^E V=(1)s×M×2E,其中,s为符号位(sign),M为尾数(mantissa或者也叫significant,a fractional value),E为阶码(exponent),或者你可以理解为指数。

注意,下图中的exp和阶码不等同,frac也和尾数不等同,这是因为阶码实际上是exp的值减去一个偏置项,尾数实际上是默认以1.开头,然后后面跟上frac。这些我们在后面会详细说。

image-20220113000127272

浮点数有这样三种:单精度双精度扩展精度(很少用)。

单精度的总共32位,使用1位的符号位,8位的阶码,23位的尾数。

image-20220113000356123

双精度的总共64位,使用1位的符号位,11位的阶码和52位的尾数。

image-20220113000617771

扩展精度的总共80位,使用1位的符号位,15位的阶码和64位的尾数。(只有Intel在用)

image-20220113000627656

编码可以根据expfrac的值分为三种(或者说四种,把第三种拆成2个)情况,最后两种是为了表示一些特殊情况的,规格化的就是我们普通的小数,而非规格化的是用来表示0附近的数。

  • 规格化的:exp不为0或者255。用来表示一般的小数
    image-20220113001130663
  • 非规格化的:exp为0。用来表示0和接近0的数字
    image-20220113001147494
  • 无穷大:exp为255,且frac为0。用来表示无穷大
    image-20220113001206224
  • NaN:exp为255,且frac不为0。用来表示Not a Number
    image-20220113001217802

接下来,我们一个一个探讨:

一、规格化的值

image-20220113001130663

这种情况下,阶码是exp的值再减去一个偏置,即 E = e − B i a s E=e-Bias E=eBias,其中e就是将exp的位模式看成是一个无符号数得到的,如果exp为k位,那么Bias就是 2 k − 1 − 1 2^{k-1}-1 2k11,比如单精度exp为8位,那么Bias就是 2 8 − 1 − 1 = 127 2^{8-1}-1=127 2811=127,而双精度为11位,那么Bias就是 2 11 − 1 − 1 = 1023 2^{11-1}-1=1023 21111=1023。也就是说,本来将exp看成是一个无符号数,那么规格化单精度的表示范围为1到254,而减去一个Bias之后,范围就变成了-126到127,双精度就是-1022到1023。

而对于尾数M,实际上M=1+f,也就是说有一个隐含的1,如果f的位模式为 f n − 1 , f n − 2 , . . . , f 0 f_{n-1},f_{n-2},...,f_0 fn1,fn2,...,f0,那么有: M = 1. f n − 1 f n − 2 . . . f 0 M=1.f_{n-1}f_{n-2}...f_0 M=1.fn1fn2...f0,所以尾数M的范围总是在 [ 1.0 , 2.0 ) [1.0,2.0) [1.0,2.0)。而之所以我们有一个隐含的1,是因为我们总是以1.开头,那么就不需要进行存储了,这样就多了一位存储空间。

二、非规格化的值

image-20220113001147494

为什么需要非规格化的值呢,假设我们都使用规格化的方式(允许exp为0),那么绝对值最小的数就是 E = − 127 , M = 1.0 E=-127,M=1.0 E=127M=1.0的时候,也就是 1.0 × 2 − 127 1.0\times2^{-127} 1.0×2127,虽然这样很靠近0了,但是无法真正表示0,所以我们采用了非规格化的值(exp全为0的情况)。这时候,我们定义阶码 E = 1 − B i a s E=1-Bias E=1Bias,而尾数 M = f M=f M=f,这时候,frac就可以被看成尾数,而没有一个隐含的1。其实无法表示0的原因就在于尾数总是有一个隐含的1,所以在非规格化的表示中把尾数定义为f是很合理的。

那么为什么阶码的定义不是像规格化中一样用e-Bias,而这时候e为0,于是 E = − B i a s E=-Bias E=Bias呢?

原因是我们希望能从非规格化的值平滑地过渡到规格化地值,使数值能够分布均匀地接近0。直观上理解一下,我们最小的规格化值是exp为00…01,那么 E = 1 − B i a s E=1-Bias E=1Bias,而f全为0,这时候M=1.0,而使用非规格化的时候,M的最大值是比M小一点的,也就是0.111…,我们只要让E和规格化的最小值保持一致,也就是 1 − B i a s 1-Bias 1Bias,那么就能够保证规格化到非规格化的过渡是平滑的,如果这里难以想象,我们后面会用一个简单的例子帮助理解。

三、特殊的值

有时候,我们需要一些特殊的值,比如无穷大,和非数字(Not a Number)

  • 无穷大
    • 正无穷
    • 负无穷

image-20220113001206224

当exp全为1的时候,我们认为是无穷大,如果s为0则是正无穷,如果为1,则为负无穷。换句话说,当指数到达了上限(全1),且frac都为0的时候,我们认为这就是最大的数值了,当然这个值是没有具体含义的,只是一个规定而已。

  • 非数字

image-20220113001217802

很多时候,我们会得到一些非实数的值,比如计算 − 1 \sqrt{-1} 1 这种根号下为负数的,我们数学中会得到复数的解,但是对于计算机而言,它就是一个非数字的(Not a Number),因此我们也需要表示这种情况。所以我们规定,当exp为全1,且frac不为0的时候,就是一个NaN。

2.4.3 数字示例

可能上面的内容有一些抽象,我们来举两个例子,当然,我们不会使用32位的,这样太复杂了,我们先使用6位的浮点数表示,其中阶码3位,尾数2位。于是偏置 B i a s = 2 3 − 1 − 1 = 3 Bias=2^{3-1}-1=3 Bias=2311=3。在数轴上画出这些可以表示的值之后,我们可以观察到,越向0靠近,点就越密集,而某几个点之间的间距是相同的。这是因为我们的指数是在改变的,而越往正负无穷靠近,指数越大,意味着两个点之间的间距就越大,因为指数相同时,两个点之间就等于他们的小数部分的差再乘以这个指数。也就是,指数相同时,这些点之间的间隔是相同的

image-20220113143434793

我们再来看一个8位的例子,其中阶码4位,尾数3位。于是偏置 B i a s = 2 4 − 1 − 1 = 7 Bias=2^{4-1}-1=7 Bias=2411=7。用表格表示一下上面的3类数:

image-20220113144933263

我们之前讲到过,我们为了使得非规格化数能够平滑地过渡到规格化数,于是使用了 E = 1 − B i a s E=1-Bias E=1Bias,从上面的表格中我们就可以体会到,最大的非规格数与最小的规格化数之间的间隔和非规格化数之间的间隔是相等的。

2.4.4 舍入

由于浮点数的范围也是有限的,对于单精度就只有32位,对于双精度就是64位,因此,我们也会面临着舍入的问题,实际上有这么几种舍入的方式:

  • 向偶数舍入:向最接近的舍入
  • 向零舍入:也就是向0找最接近的值(正数往小的找,负数往大的找)。
  • 向下舍入:也就是向下找最接近的值(找比这个数小的)。
  • 向上舍入:也就是向上找最接近的值(找比这个数大的)。

image-20220113150018256

我们默认是使用了向偶数舍入这种方式,或者它也被叫做向最接近的值舍入。当然,我们也经常用一句四舍六入五留双来概括,接下来我们稍微解释一下。

比如我们有一个数7.8949999,我们只能保留小数点后2位,其他要舍去,如果采用向偶数舍入,那么小数点后第三位是4,要找最接近的,那就是7.89。如果是7.8950001 ,那么我们会舍入到7.90,这也就是五留双,如果保留7.89,那么最低的有效位就是奇数,而我们要求是双数,所以选择近一位7.90。同样,如果是7.8850000,那么和上面一样也是留双,就变成了7.88。

上面都是十进制的例子,那么如果是二进制的呢?二进制就更加容易判断了,因为要么是0要么是1,对于0我们肯定直接舍去,而对于1,就按照上面的留双来操作了,因为二进制的1其实就是十进制中2的一半,在小数中也就类似十进制中的0.5。

比如我们现在要舍入到小数点后2位,对于 10.0001 1 2 10.00011_2 10.000112,我们发现第三位是0,那么就直接舍去,变成 10.0 0 2 10.00_2 10.002。对于 10.0011 0 2 10.00110_2 10.001102,由于要留双,那么就是 10.0 0 2 10.00_{2} 10.002。如果是 10.1110 0 2 10.11100_2 10.111002,那么就进位,变成 11.0 0 2 11.00_2 11.002

舍入就讲到这里了,还是比较简单的。

2.4.5 浮点运算

浮点乘法

浮点的乘法要简单一些,想一下,十进制的科学计数法做起乘法来是不是也挺简单的,而两者是相似的。
( – 1 ) s 1 M 1 2 E 1 × ( – 1 ) s 2 M 2 2 E 2 = ( – 1 ) s M 2 E 其 中 , s = s 1 异 或 s 2 , M = M 1 × M 2 , E = E 1 + E 2 (–1)^{s1} M_1 2^{E_1} \times (–1)^{s2} M_2 2^{E_2}=(–1)^{s} M 2^{E} \\ 其中,s=s1异或s2,M=M_1\times M_2,E=E_1+E_2 (1)s1M12E1×(1)s2M22E2=(1)sM2Es=s1s2M=M1×M2E=E1+E2
由于 M 1 × M 2 M_1\times M_2 M1×M2显然是可能大于2的,比如 1. 1 2 × 1. 1 2 1.1_2\times1.1_2 1.12×1.12就大于2了,于是我们就需要将结果右移,然后在E上加1。同时,M也要通过舍入的方式保持精度不变。

浮点加法

浮点加法的话,就需要先对齐小数点,对齐小数点的方式就是左移和右移,如果左移那么就将阶码减去对应的位数,右移就相反。我们有两种对齐方式,一种是将指数大的对齐成指数小的,另一种则相反,但是一般来说,我们会选择将指数小的往大的上对齐,比如一个是2的12次方,一个是2的10次方,我们会选择改变后者的尾数(右移2位),然后指数变成12。

选择这种对齐方式是因为,如果把小的往大的上对,即便会把小的直接舍去了然后损失一些精度,但是这样的错误会很小。

反过来就不一样了,如果把大的往小的上对,那么尾数可能会变得很大,当尾数做加法的时候,可能会产生溢出,于是就可能正数变负数,这是不能容忍的。

2.4.6 C语言中的浮点数

对于C语言而言,进行浮点数和其他数据类型转换的时候可能会产生一些预期之外的情况,我们看几个例子:

假设x为int,y为float,d为double,判断一下下面这些是否成立:

  • x == (int)(float) x 否,因为float的frac部分只有23位,而int要32位
  • x == (int)(double) x 是,因为double的frac有52位
  • f == (float)(double) f 是
  • d == (double)(float) d 否
  • f == -(-f) 是
  • 2/3 == 2/3.0 否,前者为0,后者是浮点运算。
  • d < 0.0 ⇒ ((d*2) < 0.0) 是,即便溢出变成负无穷,也是小于0
  • d > f ⇒ -f > -d 是
  • d * d >= 0.0 是
  • (d+f)-d == f 否,浮点数加法没有结合律,可能因为精度问题把f舍去,结果就是0。

本篇文章内容均来自CSAPP导读第2章,希望更加深入了解计算机系统的朋友可以点击看一看。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值