内容:浮点表示、数值示例,程序示例

 

  1. 浮点表示

IEEE浮点标准用V=-1s * M * 2E的形式来表示一个数。

  • 符号(sign): s决定数是负数(s=1)还是正数(s=0),而对于数值0的符号位解释作为特殊情况处理。
  • 有效数(significand):M是一个二进制小数,它的范围在[1,2],或者[0,1)。(为什么是这两种范围,稍后解释)。
  • 指数(exponent): E是2的幂(可能是负数),它的作用是对浮点数加权。

相对应的,浮点数的位表被划分成三个域,以编码这些值:

按高位到低位的顺序,

  • 一个单独的符号位s直接编码符号s
  • k位的指数域exp=e k-1 … e 1 e 0编码指数E。
  • n位小数域frac=f n-1…f 1f 0编码有效数M,但是被编码的值也依赖于指数域的值是否等于零。

比如,float型,s,exp,frac分别为1位,k=8位和n=23位,产生一个32位的表示。

可以得到e的范围[0,255]。

double型,s,exp,frac分别为1位,k=11位和n=52位,产生一个64位的表示。

可以得到此时e的范围[0,2047]。

根据exp的值,被编码的值可以分为三种不同的情况:

  1. 规格化值(exp>0 && exp!=2k-1,即排除最大和最小的情况

    exp此时解释为表示偏置(biased)形式的有符号整数,也就是说,指数的值实际为E=e-Bias,e即为exp的简写,位表示(ek-1 … e1 e0),而Bias是一个等于2k-1-1(单精度127,双精度1023)的偏置值。由此产生了指数的取值范围,单精度-126~+127,双精度-1022~+1023。

    E对k位统一表示为(2-2k-1~2k-1-1)。

    小数域frac解释为描述小数值f,其中0<=f<1,其二进制表示为0. fn-1…f1f0 ,也就是二进制小数点在最高有效位的左边。有效位定义为M=1+f。

    f范围:0~1-2-n

  2. 非规格化值(exp==0),即exp所有位全为0。

    此时指数值E=1-Bias。

    而小数域M=f,也就是小数域的值,而不包含开头的1。

    使指数值为1-Bias而不是0-Bias似乎很奇怪,稍后会看到,这提供了一种从非规格化值平滑转换到规格化值的方法。

    在解释这个问题之前,先考虑下之前M=1+f而不是M=f是为什么?

    如果我们希望表示的数值满足这样简单的规则:

    随着E增大或者M增大时,相对应的浮点值都是增大的。

    假设我们取M=1+f,而不是f。

    以正数时为例,若M取f值而不是1+f时,则可以表示的范围是

    min = 2E * 0 = 0;

    max = 2E * (1-2-n);

    很明显这样表示的数范围很奇怪,没有满足我们的规则。而且多个时候都是0的表示,有些混乱。

    如果取M=1+f,

    min = 2E *(1+0) = 2E;

    max = 2E * (1+1-2-n) = 2E*(2-2-n);

    则E增大时,比如Enew=E+1

    此时min=2E+1 *(1+0)= 2E *2,正好大于之前的max,同时我们发现与之前的max差值为

    2E*2n,也是指数域为E时,可表示的每个相邻数值的差值了。因此这样表示是非常平滑的。

    不过问题是,此时我们无法表示0.0了。明显是由于我们令M=1+f而不是M=f引起的,于是我们在此时令M=f。不过这样又不平滑了,于是就有了E=1-Bias,而不是直觉的E=-Bias。

    还有一个令人奇怪的问题就是,当符号位为1其他位全为0时,得到-0.0,IEEE在某些方面认为-0.0,+0.0是不同的,而其他方便是相同的……

  3. 特殊数值:

    当指数域exp全为1时,我们用来表示正无穷或者负无穷。此时不考虑小数域的情况,事实上,我们认为小数域全为0即可,因为在计算机上定义一个数为正无穷没有意义。当指数域全为1时,只需要在看下符号位来确定+∞or-∞就好了。

    1. 数值示例

      作为上面的进一步说明,举一个例子,假定浮点格式为8位,其中有k=4的指数位和n=3的小数位。偏置24-1-1=7。非规格划数的E=1-7=-6,得到2E=1/64。小数f的范围:

      (0,1/8,…/7/8),从而得到非规格划数范围0~7/8*1/64=7/512。

      8位浮点格式的非负值示例: word编辑的,表有些乱了。。。。

      描述

      位表示

      e

      E

      f

      M

      V

      0

      最小的非规格划数

       

       

       

       

      最小的非规格划数

      0 0000 000

      0 0000 001

      0 0000 010

      0 0000 011

      0 0000 110

      0 0000 111

      0

      0

      0

      0

      0

      0

      0

      -6

      -6

      -6

      -6

      -6

      -6

      -6

      0

      1/8

      2/8

      3/8

      6/8

      7/8

      0

      1/8

      2/8

      3/8

      6/8

      7/8

      0

      1/512

      2/512

      3/512

      6/512

      7/512

      最大的规格划数

       

       

       

       

       

       

       

       

       

      最大的规格划数

      0 0001 000

      0 0001 001

      0 0110 110

      0 0110 111

      0 0111 000

      0 0111 001

      0 0111 010

      0 1110 110

      0 1110 111

      1

      1

      6

      6

      7

      7

      7

      14

      14

      -6

      -6

      -1

      -1

      0

      0

      0

      7

      7

      0

      1/8

      6/8

      7/8

      0

      1/8

      2/8

      6/8

      7/8

      8/8

      9/8

      14/8

      15/8

      8/8

      9/8

      10/8

      14/8

      15/8

      8/512

      9/512

      14/16

      15/16

      1

      9/8

      10/8

      224

      240

      无穷大

      0 1111 000

      ------

      -----

      -----

      ------

      -----

      可以看到,如果将表的值的位表达式解释为无符号整数,它们就是按升序排列的,就像它们表示的浮点数一样。这不是偶然的,IEEE格式如此设计就是为了浮点数能够使用整数排序函数来进行排序。

      有几个固有的属性:

      1. 值+0.0总有一个全为0的位表示
      2. 最小的正非规划值有一个位表示,是由最低有效位为1而其他所有位为0构成的。它具有小数(和有效数)值M=f=2 -n和一个指数值E=-2 k-1+2。
      3. 最大的非规格化值位模式有全为0的指数域和全为1的小数域组成。仅比最小的正规格化值小一点。
      4. 最小的正规格化值位模式的指数域的最低有效位为1,其余全为0.有效值M=1.
      5. 最大的规格化值的位表示的符号位为0,指数的最低有效位位0,其他全为1.它的小数值f=1-2 -n,有效值M=2-2 -n,指数值E=2 k-1-1。
    2. 程序示例

      程序分别打印了12345以及12345.0的位表示。

      打印了两者位相同的最长部分,以及几个比较大的浮点数。

      程序本身不重要,直接看下结果:

      zhy@desktop:~/doublemint/factory$ ./a.out

      0 0000 0000 0000 0000 0110 0000 0111 001

      0 1000 1100 1000 0001 1100 1000 0000 000

      1001 1100 0000 1

      16777216.000000

      16777216.000000

      33554432.000000

    12345,12345.0重复的部分从第10位开始,恰好是12345除去最高位1后的部分。

    12345具有二进制表示[11000000111001]。通过向二进制小数点右边移动13位,我们创建这个数的一个规格化表示,得到12345 = 1.10000001110012x213。为了用IEEE单精度形式来编码,我们丢弃开头的1,并且在末尾增加10个0,来构造小数域,得到二进制表示[1000001110010000000000].为了构造指数域,我们增加偏置量127到13,得到140,其二进制表示位[10001100]。加上符号位0,就得到二进制的浮点表示:

    0 1000 1100 1000 0001 1100 1000 0000 000

    现在我们可以看到,相关的区域对应整数的低位,刚好在最高的等于1的位之前停止(这个位就是隐含的开头的位1),和浮点表示的小数部分的高位是相匹配的。

再举个例子,0.5浮点位怎么表示的?

符号位为0

0.5=1*2-1*(1+0),对应与E=-1的情况,再加上Bias,[01111110]

小数位为[0000…000]

因此0 0111 1110 0000 0000 0000 0000 0000 000

1.5 = 1* 1 * (1 + 2-1),E=0,即[01111111]。

小数位为[1000…0000]

因此0 0111 1111 1000 0000 0000 0000 0000 000。

12345 = 1.10000001110012x213

可以得到12345.5 = 1.100000011100112x213

因此12345.5的浮点数表示为: 0 1000 1100 1000 0001 1100 1100 0000 000

 

最后打印的浮点数跟下题有关:

假定一个k位指数和n位小数的浮点格式,给出不能准确描述的最小正整数的公式(因为要想正确表示它需要n+1位小数),对于单精度格式这个整数是多少?

这个数字的二进制表示为:1后面跟着n个0,其后再跟1,得到值是2n+1+1。

当n=23时,值为224+1。


#include <stdio.h>
#include 
<vector>
#include 
<iostream>
using namespace std;

typedef unsigned 
char *byte_pointer;
typedef vector
<int> vint;

void show_bytes(byte_pointer start, int len)
{
    
int i;
    
for ( i=0; i<len; ++i)
        printf(
" %.2x",start[i]);
    printf(
"\n");
}


template 
<typename T>
void show(T x)
{
    show_bytes((byte_pointer) 
&x,sizeof(T));
}


void getBinaryVector(vector<int> &v, byte_pointer start, int len)
{
    v.clear();
    
for ( int i=0; i<len; ++i) {
        
char c = start[i];
        
for ( int j=0; j<8++j) {
            v.push_back(c
&1);
            c
>>=1;
        }

    }

}


template 
<typename T>
void showBinaryVector(vector<int> &v, T x)
{
    getBinaryVector(v, (byte_pointer) 
&x, sizeof(T));
    
for ( int i=v.size()-1; i>=0--i) {
        printf(
"%d",v[i]);
        
if (i%4==3) printf(" ");
    }

    printf(
"\n");
}


void getSameSubVector(vint &v1, vint &v2, vint &vsub)
{
    vint temp;
    
for ( int i=0; i<v1.size(); ++i)
        
for ( int j=0; j<v1.size(); ++j) {
            
if (v1[i]==v2[j]) {
                temp.push_back(v1[i]);
                
int tempi = i;
                
int tempj = j;
                
while (v1[++tempi]==v2[++tempj]) {
                    temp.push_back(v1[tempi]);
                }

                
if (vsub.size() < temp.size()) {
                    vsub.swap(temp);
                }

                temp.clear();
            }

        }

}


int main()
{
    
int a = 12345;
    
float b = 12345.0;

    vector
<int> v1;
    showBinaryVector(v1,a);

    vector
<int> v2;
    showBinaryVector(v2,b);

    vint vsub;
    getSameSubVector(v1,v2,vsub);
    
for ( int i=0; i<vsub.size(); ++i) {
        printf(
"%d",vsub[i]);
        
if (i%4==3) printf(" ");
    }

    printf(
"\n");

    
float t1 = 16777216;
    
float t2 = 16777217;
    
float t3 = 33554432;
    printf(
"%f\n",t1);
    printf(
"%f\n",t2);
    printf(
"%f\n",t3);

    
return 0;
}