浮点数定义

  • 十进制小数的表示:$d_{m}d_{m-1}…d_{1}d_{0}.d_{-1}d_{-2}…d_{-n}$。其值为:$\sum_{i=-n}^{m}d_{i}2^{i}$。表示有$m+1$位整数$n$位小数。

  • 二进制小数表示:$b_{m}b_{m-1}…b_{1}b_{0}.b_{-1}b_{-2}…b_{-n}$。转换为十进制值为$\sum_{i=-n}^(m)b_{i}2^{i}$。同样表示有$m+1$位整数部分$n$位小数部分

  • 这种表示方式是浮点数的定点表示。缺点是不能表示非常大的数字。例如表示$5*2^{100}$,就需要101.之后有100个0的位模式才行。

  • 基于这种缺点,IEEE浮点标准用 $$V=(-1)^s * M * 2^E$$的形式表示一个数。只需要对$s,M$和$E$使用一定长度的位进行编码就可以表示一个较大的数。

  • C语言对于单精度的浮点数的表示$s$为1位,$E$为8位,$M$为23位。

  • 符号位$s$编码为1表示负数编码为0表示非负数。

  • 阶码$E$可以取正或负,那么阶码编码有两种选择,一是和整数一样使用补码,另一种是先使用无符号编码然后将其映射到一个包含负数的集合,即减掉一个偏置量。

  • 根据IEEE浮点标准$$V=(-1)^s * M * 2^E$$,可以通过调整阶码$E$的范围,将尾数$M$表示为$1.b_{-1}b_{-2}…b_{-n}$或$0.b_{-1}b_{-2}…b_{0}$,编码为$1.f_{n-1}f_{n-2}…f_{0}$或者$0.f_{n-1}f_{n-2}…f_{0}$,这样的编码方式可以增加一位精度,因为最高位的1或者0不用编码。

  • 这是浮点数的一些具体参考: 浮点数的详细信息

问题

  • 在浮点数定义中为什么要对阶码编码使用偏置,而不是直接使用补码?还有阶码偏置的选取有什么意义?

    • 假设阶码编码使用补码方式:
      • 对于单精度的浮点数,阶码编码使用8位二进制表示,数值范围是$-128$$127$。其中0x00~0x7F表示正数,对于阶码的编码则表示$2^0$$2^{127}$,而对于0xF0~0xFF表示负数,对于阶码的编码则表示$2^{-128}$~$2^{-1}$。这样看起来好像也没有什么问题。
      • 浮点数运算步骤:0操作数检查,比较阶码大小并对阶,尾数进行加减运算,结果规格化并进行舍入处理。
      • 可以看出浮点数运算有关键的一步是比较阶码大小并对阶。如果采用补码编码,这就要使用补码比较大小的方式:先比较符号位(正数大于负数),其次比较符号位外的其他位(除符号位以外从最高位向最低位比较先出现1的数值大)。
      • 但是采用偏置的方式可以取消调符号位的比较,直接进行其他位的比较。
      • 同时还有一个问题,是对浮点数1.0和0.0的编码。如果尾数默认拥有前缀1的话,那么0.0无法表示(因为全0编码表示1.0),同时如果尾数拥有前缀0,1.0就无法表示(因为全0编码表示0.0)。
      • 如果你说补码表示的阶码位全0表示非规格化数,全1表示其他数,而剩下的位模式表示规格化数,因为非规格化数的阶码部分只能表示$2^0=1$,数的范围完全由尾数决定,这样表示的小数范围会比使用偏置小,同时非规格化数有时会比规格化数大(阶码的编码为0x01~0x7F),有时会比规格化数小(0xF0~0xFE),很凌乱。同样的,对于规格化数,阶码编码为0x01~0x7F,数值范围为$2^0$$2^{127}$表示比1大的数,编码为0xF0~0xFE,数值范围为$2^{-128}$$2^{-2}$,表示绝对值比1小的数,同时这样在执行阶码对阶加减的时候需要使用补码加减法(先将位模式转换为无符号数,执行无符号数加减,如果溢出则截断,然后再转为补码)。
    • 总而言之,如果使用补码进行编码,则会导致浮点数数值大小和位模式很混乱,阅读性差,同时对阶码作运算也没有偏置(无符号数)方便。
    • 接下来,分析使用对无符号数进行偏移来编码阶码:
      • 如下图所示: 可以看出8位的阶码位的无符号数范围$0$~$255$,其中全0和全1分别表示非规格化和其他数字,其他位模式则表示规格化数。同时对于全0尾数默认前缀为0,对于规格化数尾数前缀默认为1,这样的设置会增加1位精度。
      • 对于规格化数的阶码位的无符号数范围$1$~$254$,刚好254个数。而由于阶码可正可负,IEEE采用的是正数1~127非正数-126~0各占127,而非补码编码的负数非负数。如果负数占位更多的话,会使浮点数表示的值更接近0(假如负数和非负数各占127,在尾数编码全0(数值为1.0)的时候,阶码取最小值,浮点数为$1.0 * 2^{-127}$ 和 $1.0 * 2^{-126}$进行比较),这样明显没有比正数大多有意义,阶码正数大则阶码表示的最大值就会较大为$2^{127}$(和$2^{126}$比较),从而浮点数的范围就更大一些。
      • 现在的问题是如何将规格化数的阶码为的无符号数$1$$254$映射到$-126$$127$。很明显给$1$~$254$减掉一个偏置Bias,和明显该偏置为Bias=127,这个偏置值刚好是$2^{k-1}-1$,k为阶码位数。这其中有什么关联吗?
      • 首先k位无符号数编码的个数是$2^k$,数值范围是$0$$2^k-1$,需要去掉全0和全1,那么用于规格化数的个数就是$2^{k}-2$,数值范围是$1$$2^k-2$,然后一半用来表示正数,所以就需要取数值范围最大的数的一半作为偏移即$\frac {2^k-2} {2}$,正数的个数就是$\frac {2^{k}-2} {2}$,那么这个偏移量就等于正数的个数。这样就解释了偏移量的原因。
      • 同时由于是对无符号数使用偏移表示阶码的原因,所以阶码的运算都是采用无符号的运算方式,相对于补码来说,去掉了比较符号位的操作。
  • 非规格化(阶码位全0)为什么要采用1-Bias作为阶码数值,同时尾数前缀不再为1而是0?

    • 首先总体而言,这是为了浮点表示数值从小到大的平滑过渡。
    • 对于单精度浮点数来说,尾数为23位,非规格化尾数编码最大是全1:如果尾数前缀为0,则其数值为$1-2^{-23}$;如果尾数前缀为1,则其数值为$1+1-2^{-23}$。而对于规格化数来说,前缀为1,尾数最小编码是全0,其数值为1。所以在阶码相同的前提下,如果非规格化尾数前缀为1,采用尾数最大编码全1,会比规格化数尾数前缀为1最小编码全0的数值大,这明显是不合理的,而非规格化数尾数前缀采用0的话,最大编码数值刚好比规格化最小编码数值小$2^{-23}$。当然这一切的前提是阶码相同,才会有这种过渡。
    • 对于单精度浮点数来说,非规格化数采用1-Bias作为阶码,阶码数值是$2^{-126}$,这样尾数编码采用最大全1的情况下,浮点数值为$(1-2^{-23}) * 2^{-126}$如果使用-Bias作为阶码,则阶码数值是$2^{-127}$,这样尾数编码同样采用最大全1,浮点数值为$(1-2^{-23}) * 2^{-127}$。而最小的规格化数是$1.0 * 2^{-126}$,很明显,采用-Bias数据不会平滑过渡。
  • 下面是$k=4$的阶码位和$n=3$的小数位的非负数表示,偏移量位7。