利用共用体和位段获得IEEE标准编码的浮点型数据各部分数值

前面《浮点型数据(float, double)存储IEEE标准解析和应用》(http://www.cnblogs.com/zhugehq/p/5918599.html) 一文说明了IEEE标准中浮点型数据的编码方式。本文将利用共用体和位段的知识,直接在程序中打印出浮点型数据被编码后的数值。

首先来看单精度浮点数,共用体的定义如下所示:

typedef union FloatNumber
{
  float f;
  struct
  {
    unsigned d : 23;
    unsigned e : 8;
    unsigned s : 1;
  };
}FloatNumber;

单精度浮点型变量f和无名结构体共享一块4字节的内存。改变f的数值,将会影响到无名结构体内部成员(d代表数据位,e代表指数位,s代表符号位)的数值。此时无需考虑f在内存中的大小尾存储方式,因为系统虽然是以小尾方式存储f,但是对无名结构体来说(无名结构体三个成员(位段)的地址相同,都是f的地址),依然是按照s、e、d从左向右的顺序来编码,并不受f小尾存储方式的影响。

有了这个共用体,我们可以改动f的数值,接着输出s、e、d各位的数值,从而验证前文分析过的IEEE浮点数编码标准。无名结构体的成员(位段)的值默认是int类型。由于printf函数可以把数据以十进制、十六进制或者八进制输出,却唯独不支持以二进制形式输出,所以此时不得不另外想办法。参考此问题的答案(http://stackoverflow.com/questions/111928/is-there-a-printf-converter-to-print-in-binary-format),借助宏来实现二进制数据的输出:

#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte)  \
  (byte & 0x80 ? '1' : '0'), \
  (byte & 0x40 ? '1' : '0'), \
  (byte & 0x20 ? '1' : '0'), \
  (byte & 0x10 ? '1' : '0'), \
  (byte & 0x08 ? '1' : '0'), \
  (byte & 0x04 ? '1' : '0'), \
  (byte & 0x02 ? '1' : '0'), \
  (byte & 0x01 ? '1' : '0') 

把此宏稍加修改,以适用于本文解析单精度浮点数编码的情况,最终的程序代码如下所示:

#define FLOAT_S_TO_BINARY_PATTERN "%c"
#define FLOAT_S_TO_BINARY(float_s)  \
  (float_s & 0x01 ? '1' : '0') 

#define FLOAT_E_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define FLOAT_E_TO_BINARY(float_e)  \
  (float_e & 0x80 ? '1' : '0'), \
  (float_e & 0x40 ? '1' : '0'), \
  (float_e & 0x20 ? '1' : '0'), \
  (float_e & 0x10 ? '1' : '0'), \
  (float_e & 0x08 ? '1' : '0'), \
  (float_e & 0x04 ? '1' : '0'), \
  (float_e & 0x02 ? '1' : '0'), \
  (float_e & 0x01 ? '1' : '0') 

#define FLOAT_D_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c"
#define FLOAT_D_TO_BINARY(float_d)  \
  (float_d & 0x400000 ? '1' : '0'), \
  (float_d & 0x200000 ? '1' : '0'), \
  (float_d & 0x100000 ? '1' : '0'), \
  (float_d & 0x80000 ? '1' : '0'), \
  (float_d & 0x40000 ? '1' : '0'), \
  (float_d & 0x20000 ? '1' : '0'), \
  (float_d & 0x10000 ? '1' : '0'), \
  (float_d & 0x8000 ? '1' : '0'), \
  (float_d & 0x4000 ? '1' : '0'), \
  (float_d & 0x2000 ? '1' : '0'), \
  (float_d & 0x1000 ? '1' : '0'), \
  (float_d & 0x800 ? '1' : '0'), \
  (float_d & 0x400 ? '1' : '0'), \
  (float_d & 0x200 ? '1' : '0'), \
  (float_d & 0x100 ? '1' : '0'), \
  (float_d & 0x80 ? '1' : '0'), \
  (float_d & 0x40 ? '1' : '0'), \
  (float_d & 0x20 ? '1' : '0'), \
  (float_d & 0x10 ? '1' : '0'), \
  (float_d & 0x08 ? '1' : '0'), \
  (float_d & 0x04 ? '1' : '0'), \
  (float_d & 0x02 ? '1' : '0'), \
  (float_d & 0x01 ? '1' : '0') 
  
typedef union FloatNumber
{
  float f;
  struct
  {
    unsigned d : 23;
    unsigned e : 8;
    unsigned s : 1;
  };
}FloatNumber;

int _tmain(int argc, _TCHAR* argv[])
{
  FloatNumber number;

  number.f = 3.5;

  printf(FLOAT_S_TO_BINARY_PATTERN " ", FLOAT_S_TO_BINARY(number.s));

  printf(FLOAT_E_TO_BINARY_PATTERN " ", FLOAT_E_TO_BINARY(number.e));

  printf(FLOAT_D_TO_BINARY_PATTERN, FLOAT_D_TO_BINARY(number.d));

  return 0;
}

编译运行后,得到单精度浮点数3.5的编码,和上一篇文章里的结果一致:

至于如何利用共用体和位段对双精度浮点数进行解析,和单精度浮点数只是位段长度的不同,就不再说明了。

最后,分享一个很优雅的将任意数据类型的数变为二进制输出的函数(http://stackoverflow.com/questions/111928/is-there-a-printf-converter-to-print-in-binary-format)。这个函数考虑到数据在内存中的小尾存储方式,以从高地址向低地址的方向,对每一字节进行提取和读数。通过这样直接的方式,就不用再去考虑任何和编码标准相关的因素了,所以它适用于任何数据类型。

//assumes little endian (假设是小尾存储方式)
void printBits(size_t const size, void const * const ptr)
{
    unsigned char *b = (unsigned char*) ptr;
    unsigned char byte;
    int i, j;

    for (i=size-1;i>=0;i--)
    {
        for (j=7;j>=0;j--)
        {
            byte = (b[i] >> j) & 1;
            printf("%u", byte);
        }
    }
    puts("");
}

//测试
int main(int argv, char* argc[])
{
        int i = 23;
        /* uint ui = UINT_MAX; */ //可能是UNIX数据类型
        float f = 23.45f;
        printBits(sizeof(i), &i);
        //printBits(sizeof(ui), &ui);
        printBits(sizeof(f), &f);
        return 0;
}

将此代码复制到VS2012中进行编译,运行得到单精度浮点数23.45的编码(第二行),然后再用本文说明的共用体和位段的方式去解析23.45的编码,发现两个程序的输出是一样的:

真是条条大路通罗马啊。

posted @ 2016-10-12 00:22  zhugehq  阅读(873)  评论(0编辑  收藏  举报