csapp ch02 (part 4): Floating Point

这篇文章是csapp第二章第四节的阅读笔记。

接下来看看浮点数的表示。

1. 二进制小数

首先看看有小数部分的二进制数:
$$
b_mb_{m-1}\cdots b_1b_0.b{-1}b{-2}\cdots b_{-n}
$$
和无符号整数类似,这个二进制小数的值可以这样计算:
$$
b=\sum_{i=-n}^m2^i\times b_i
$$
比如:$101.11_2=1\times 2^2+0\times 2^1+1\times 2^0+1\times 2^{-1}+1\times 2^{-2}=5\frac{3}{4}$。

2. IEEE浮点数表示

使用上面的方法无法表达很大的数,因此使用类似$x\times 2^y$的形式来表示。

IEEE浮点数就是使用这种形式:$V=(-1)^s\times M\times 2^E$。

  • $s$:表示符号(sign),如果$s=0$代表这个数是正数;$s=1$表示负数;
  • $M$:尾数(significant),是一个二进制小数,范围是1到$2-\epsilon$或0到$1-\epsilon$;
  • $E$:阶码(exponent),将整个数的值乘以$2^E$。

这样一个浮点数的二进制表示就有三个部分。下图表示的是C语言中单精度浮点数float和双精度浮点数double的二进制形式:

Standard floating-point formats

即使用一个bit表示$s$,k个bit表示$E$和n个bit表示$M$。其中单精度下$k=8,n=23$,双精度下$k=11,n=52$。

使用上面的格式编码的浮点数,对应的值有三种情况。

2.1 规格化的值

这是最普通的情况,这时exp既不都是0也不都是1,这时,阶码$E=e-Bias$,其中$e$就是exp对应的十进制数,而$Bias$是$2^{k-1}-1$,单精度下是127,双精度下是1023。

同时,尾数$M=1+f$,其中$f$就是frac部分编码的 。这样$M$的二进制形式就是$1.f{n-1}f{n-2}\cdots f_0$。

2.2 非规格化的值

exp全是0时,这个浮点数就是非规格化的值。这时。阶码$E=1-Bias$,而尾数$M=f$。

非规格化的值可以表示0。规格化的值不能表示0因为尾数$M=1+f>0$。当expfrac都是0时就表示0。同时$s$的存在,还可以表示-0.0和+0.0。

非规格化的值还可以表示非常接近0的值。

2.3 特殊值

exp都是1,的时候,可以表示一些特殊值。比如frac都是0的时候,可以表示无穷。同时根据$s$的值可以表示正无穷和负无穷。

frac不全是0的时候,表示NaN,not a number。这个值可以表示$\sqrt{-1}$和$\infty-\infty$的值。

3. 示例数字

下面的表格就演示了IEEE浮点数的表示方法(其中k=4,n=3,Bias=7):

Description Bits $e$ $E$ $2^E$ $f$ $M$ $2^E\times M$ Decimal
Zero 0 0000 000 0 -6 $\frac{1}{64}$ $\frac{0}{8}$ $\frac{0}{8}$ $\frac{0}{512}$ 0.0
Smallest positive 0 0000 001 0 -6 $\frac{1}{64}$ $\frac{1}{8}$ $\frac{1}{8}$ $\frac{1}{512}$ 0.001953
0 0000 010 0 -6 $\frac{1}{64}$ $\frac{2}{8}$ $\frac{2}{8}$ $\frac{2}{512}$ 0.003906
0 0000 011 $\frac{1}{64}$ $\frac{3}{8}$ $\frac{3}{8}$ $\frac{3}{512}$ 0.005859
$\vdots$
Largest denormalized 0 0000 111 0 -6 $\frac{1}{64}$ $\frac{7}{8}$ $\frac{7}{8}$ $\frac{7}{512}$ 0.013672
Smallest normalized 0 0001 000 1 -6 $\frac{1}{64}$ $\frac{0}{8}$ $\frac{8}{8}$ $\frac{8}{512}$ 0.015625
0 0001 001 1 -6 $\frac{1}{64}$ $\frac{1}{8}$ $\frac{9}{8}$ $\frac{9}{512}$ 0.017578
$\vdots$
0 0110 110 6 -1 $\frac{1}{2}$ $\frac{6}{8}$ $\frac{14}{8}$ $\frac{14}{16}$ 0.875
0 0110 111 6 -1 $\frac{1}{2}$ $\frac{7}{8}$ $\frac{15}{8}$ $\frac{15}{16}$ 0.9375
One 0 0111 000 7 0 1 $\frac{0}{8}$ $\frac{8}{8}$ $\frac{8}{8}$ 1.0
0 0111 001 7 0 1 $\frac{1}{8}$ $\frac{9}{8}$ $\frac{9}{8}$ 1.125
0 0111 010 7 0 1 $\frac{2}{8}$ $\frac{10}{8}$ $\frac{10}{8}$ 1.25
$\vdots$
0 1110 110 14 7 128 $\frac{6}{8}$ $\frac{14}{8}$ $\frac{1792}{8}$ 224.0
Largest normalized 0 1110 111 14 7 128 $\frac{7}{8}$ $\frac{15}{8}$ $\frac{1920}{8}$ 240.0
Infinity 0 1111 000

使用这种方式,可以保证随着二进制编码的递增,对应的浮点数的值也是递增的(负数反过来)。

这样对于浮点数的排序就可以直接对二进制编码进行排序了。

同时,对于非规格化我们使用$1-Bias$作为$E$,过渡到规格化时使用$e-Bias$,可以看到一个渐进递增的关系。

作为一个例子,下面演示把一个整数编码转换成对应的浮点数编码。

  1. 整数3510593的十六进制编码是0x00359141,把它转换成对应的单精度编码。

  2. 首先二进制编码是:0000,0000,0011,0101,1001,0001,0100,0001

  3. 可以表示成$1.1010,1100,1000,1010,0000,1_2\times 2^{21}$;

  4. 那么位数M就是1.1010,1100,1000,1010,0000,1,需要右移21位,所以$E=21$;

  5. 这是一个规格化的数,所以f就是0.1010,1100,1000,1010,0000,1

  6. 单精度下frac一共23位,所以f需要补0:0.1010,1100,1000,1010,0000,100,这样就得到了frac的编码;

  7. 规格化下$E=e-Bias$,其中$Bias=127$,所以$e=21+127=148=1001,0100_2$,所以exp的编码就是1001,0100

  8. 最后这是一个正数,所以$s=0$,得到对应浮点数的编码:0100,1010,0101,0110,0100,0101,0000,0100,十六进制就是0x4A564504

4. 舍入(Rounding)

因为精度和位数的限制,浮点数只能近似地表示实数运算,所以浮点数运算涉及到一个舍入的问题。下表展示了四种舍入的方式:

Mode 1.40 1.60 1.50 2.50 -1.50
Round-to-even 1 2 2 2 -2
Round-toward-zero 1 1 1 2 -1
Round-down 1 1 1 2 -2
Round-up 2 2 2 3 -1

其中后三种都好理解,不好理解的是第一种:Round-to-even。

为什么要向偶数舍入呢?考虑这个场景。我们需要计算一组数的平均数,如果采用下面的三种舍入方法,那么对于同样符号(正负)的数来说,舍入的方向是一样的,这样在计算平均值的时候带来的偏差会比较大。相反,如果使用第一种,即向偶数舍入,那么一组数大概率50%的数向上舍入50%的数向下舍入,平衡了舍入带来的偏差。不过这种舍入方式计算的平均值也比真实值略高一些。

5. 浮点数运算

IEEE浮点数的运算是这样的,将两个浮点数当做看作两个实数$x,y$,对于加法就计算$Round(x+y)$。也就是先计算然后舍入。

不过由于有限的精度,可以不需要完全计算出结果,只要精度够了即可。

像有符号和无符号整数的加法一样,浮点数的加法也构成一个阿贝尔群,只不过由于舍入的存在,令$x+^fy$定义为$Round(x+y)$,需要考虑如下的特点:

  • 由于溢出,$x+^fy$可能得到无穷;

  • 可交换:$x+^fy=y+^fx$;

  • 不可结合:$(x+^fy)+^fz$和$x+^f(y+^fz)$不一定相等。比如(3.14+1e10)-1e10的结果是0.0,而3.14+(1e10-1e10)的结果是3.14

  • 由于不可结合,因此编译器对于浮点数就不能做一些优化。比如:

    x = a + b + c;
    y = b + c + d;

    就不能优化成:

    t = b + c;
    x = a + t;
    y = t + d;
  • 浮点数加法满足单调性:如果$a\geq b$,那么对于不是NaNx来说,有$x+^fa\geq x+^fb$;

浮点数的乘法也具有和浮点数的加法一致的上述特征。同时,对于浮点数乘法来说,$a\ast ^fa\geq0$,这对于无符号和有符号整数的乘法来说不满足。

6. C语言中的浮点数

C语言中有两种浮点数:floatdouble,两者在和int做类型转换时,原则如下:

  • intfloat:不会溢出,但是会舍入;
  • intfloatdouble:没有精度损失;
  • doublefloat:值可能会变成正无穷或负无穷,因为可表示范围小了,同时也会有舍入,因为精度也小了;
  • floatdoubleint:值会向0舍入。比如1.999变成1-1.999变成-1。也有可能溢出,不过C语言没有规定溢出后的int值,与Intel兼容的处理器会将结果指定为$TMin_w$。转换浮点数到整数的时候,如果找不到一个合理的值,就使用这个值。因此(int) +1e10的结果是-2147483648

通过下面的几个例子做一下练习(其中xfd分别是intfloatdouble类型的值):

Expression True or False Explanation
x==(int)(double)x double的精度大于int
x==(int)(float)x x=TMax时错误
d==(double)(float)d d=1e40时,结果是正无穷
f==(float)(double)f double的精度大于float
f==-(-f) 浮点数使用s表示正负
1.0/2==1/2.0 计算的时候两个数都会转换成浮点数
d*d>=0.0 正确,即使会溢出
(f+d)-f==d 这个就是不符合结合律的例子了

注意:将大的浮点数转换成整数是一个常见的错误


 Previous
csapp ch02 (part 5): Summary csapp ch02 (part 5): Summary
这篇文章是基于csapp第二章的习题做的一个总结,习题以及答案在这里。 1. 信息的存储计算机中信息的存储就是bit,每8个bit构成一个byte,所有的空间都是由一系列byte组成的。几个byte就可以表示一个信息,具体的信息就需要相
2020-05-21
Next 
csapp ch02 (part 3): Integer Arithmetic csapp ch02 (part 3): Integer Arithmetic
这篇文章是csapp第二章第三节的阅读笔记。 整数的运算包括加减乘除,对于每一种运算都涉及到有符号整数和无符号整数。 这里涉及到的符号: Symbol Type Meaning $+_w^t$ Operation 补码加法
2020-05-09
  You Will See...