Eureka!

Keep thinking.

导航

查看数值的二进制编码

Posted on 2008-10-24 01:24  Kid.Loki  阅读(1043)  评论(0编辑  收藏  举报

写了一个模板函数,用于查看各种类型的数值的二进制编码。主要利用了C++的union。

 1 template <typename Type>
 2 string parseBinary( Type target,
 3                           const DisplayFormat &format = DisplayFormat(), 
 4                           string format_separator = "," )

 5 {
 6     const size_t BYTES = sizeof(Type);
 
7         
 8     union {
 9         Type value;
10         unsigned char ch[BYTES];
11     } u;
12
     u.value = target;
13 
14     bitset<BYTES*8> binary;
15     unsigned char mask;
16     for(size_t i=0; i!=BYTES; ++i) {
17         mask = 0x01;
18         for(size_t j=0; j!=8; mask<<=1++j) {
19             if((u.ch[i] & mask) == mask)
20                 binary.set(8*i+j);
21         }
22     }
23 
24     return format(binary.to_string(), format_separator);
25 }

第2行的DisplayFormat是一个仿函数,第3行是分隔符,都是格式化输出用的,暂时忽略不计,所以parseBinary的功能是对任意类型的输入target,获取并返回它的二进制编码。

第8~11行是整个函数的主要内容,把目标数值分解成一个一个的字节(unsigned char型占1字节)。这里会遇到“低位低地址,高位高地址”的问题,以int型数(4字节)1023为例:

00000000 00000000 00000011 11111111  (u.value) = 1023
-------------------------------------------------   →   union
 u.ch
[3]    u.ch[2]    u.ch[1]     u.ch[0]   ( u.ch  )

binary
[31]    …    …    …    …    binary[0]                   bitset<32>

如图中展示的,u.ch[0]应该是0xFF,而u.ch[3]是0x00。注意了这一点就一切好办,第14~22行就是把各位的0和1原样复制到binary(bitset<32>类型)中,最低位binary[0]=1,最高位binary[31]=0。

第24行输出。这里bitset<*>的一个特性比较好:它的to_string()成员函数会将bitset<*>对象逆序转化成string,即高位在前低位在后,这样得到的string更符合我们的直观。

过程中之所以使用bitset<*>是出于空间的考虑。bitset<*>的一个元素只占1位,而且和其他STL容器的空间分配策略类似,bitset<*>以4字节为单位追加空间,所以最坏的情况下也只会浪费3个字节。bitset<*>当然可以被int数组替代,但是int数组的空间代价是bitset<*>的32倍。另外,用int数组代替bitset<*>时需要手动处理逆序问题。

对于string类型以及字符串常量的输入,parseBinary有下列两个重载版本。

 1 string parseBinary(string target, 
 2                          const DisplayFormat &format = DisplayFormat(), 
 3                          string format_separator = ","
 4 {
 5     string result = "";
 6     size_t target_size = target.size();
 7     for(size_t i=0; i!=target_size; ++i)
 8         result += parseBinary(target[i], format, format_separator);
 9     return result;
10 }
11 
12 string parseBinary(const char *ptarget,
13                          const DisplayFormat &format = DisplayFormat(), 
14                          string format_separator = ",")
15 {
16     return parseBinary(string(ptarget), format, format_separator);
17 }

默认情况下,输出是一个未经分割的0-1序列。不过有时候插入一些分隔符会有助于阅读。parseBinary的第2、3个参数提供这样的自定义途径。基类格式DisplayFormat定义如下,这是一个无格式的返回。

1 struct DisplayFormat {
2     virtual string operator() (string s, string separator = ","const { return s; }
3 };
在DisplayFormat的基础上可以定义若干子类,进行各种格式化,例如,按字节输出:

 1 struct Bytewise: public DisplayFormat {
 2     string operator() (string s, string separator = ","const {
 3         size_t s_size = s.size();
 4         if(s_size%8 != 0return s;
 5         
 6         string result = "";
 7         for(size_t i=0; i!=s_size; i+=8)
 8             result += (s.substr(i, 8+ separator);
 9 
10         return result.substr(0, result.size()-1);
11     }
12 };
对于浮点型(float和double),还有符合IEEE754规范的输出格式:

 1 struct IEEE754_Float: public DisplayFormat {
 2     string operator() (string s, string separator = ","const {
 3         if(s.size() != 32return s; 
 4         return (s.substr(0,1+ separator + s.substr(1,8+ separator + s.substr(9));    
 5     }
 6 };
 7 
 8 struct IEEE754_Double: public DisplayFormat {
 9     string operator() (string s, string separator = ","const {
10         if(s.size() != 64return s;
11         return (s.substr(0,1+ separator + s.substr(1,11+ separator + s.substr(12));  
12     }
13 };

 下面是一些测试。

1  cout << parseBinary(-127, Bytewise(), " ") << endl;
2  cout << parseBinary('a') << endl;
3  cout << parseBinary(3.0f, IEEE754_Float()) << endl;
4  cout << parseBinary(-3.0, IEEE754_Double(), "-") << endl;
5  cout << parseBinary(wchar_t('ab')) << endl;
6  cout << parseBinary("wxyz") << endl;
7  cout << parseBinary("中国", Bytewise()) << endl;

对应的结果如下:

1 11111111 11111111 11111111 10000001
2 01100001
3 0,10000000,10000000000000000000000
4 1-10000000000-1000000000000000000000000000000000000000000000000000
5 0110000101100010
6 01110111011110000111100101111010
7 11010110,11010000,10111001,11111010
其中,第1行是整型数-127的补码;第2行是字符‘a’的ASCII码;第7行是“中国”的GBK编码D6D0,B9FA。

 

遇到的主要问题:

error C2620:“union有用户定义的构造函数或非平凡默认构造函数。”

这个问题是试图直接将bitset<*>放进union造成的。我的初步理解是构造函数的自动调用会不受用户控制地修改union数据。

error C2765:“函数模板的显式专用化不能有任何默认参数。”

这个问题是试图把处理字符串的两个parseBinary实现为函数模板的专用化版本时出现的。我对这个error的深层次原因比较感兴趣,等有空再慢慢查找资料。作为替代方案,我把针对字符串的两个parseBinary做成重载而非专用化。 

warning C4239:“使用了非标准的扩展。”

如果不把parseBinary的第二个参数声明为const则会产生这样的问题。最终调用parseBinary的时候传进DisplayFormat或其子类的临时对象是比较整洁的做法,但C++不鼓励把非常量引用绑定到临时对象。第二个参数声明为const后,DisplayFormat及其子类的operator()也要连带着声明为const,否则会引发error C3848,这一点不难理解:非const的operator()有可能改变对象值,从而对象整体的const性质得不到保证。