zoukankan      html  css  js  c++  java
  • 利用共用体和位段获得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的编码,发现两个程序的输出是一样的:

    真是条条大路通罗马啊。

  • 相关阅读:
    mysql性能优化
    java技术路线
    浅谈分布式事务
    java图片压缩
    centos6.8 固定IP
    Mybatis批量插入返回自增主键(转)
    MySQL创建用户的三种方法 (并授权)转
    MyBatis SQL xml处理小于号与大于号
    MySQL数据库引擎MyISAM和InnoDB的区别介绍
    Gson学习文档
  • 原文地址:https://www.cnblogs.com/zhugehq/p/5951301.html
Copyright © 2011-2022 走看看