「工具箱」Bit Hacks

昨晚看到的博客,Peteris Krumins 谈到了个人工具箱的重要性。比较有启发。

既然目前的个人知识储备以及实践经验,还比较不成系统,那就一点点开始,慢慢来好了。

第一篇,就以昨天看到的这篇 Bit Hacks 起始。原理部分不多做解释,如果熟悉二进制补码形式的数位表示方法,就可以轻松理解。如下是我写的头文件(省略了头部注释),与上述链接中的实现有些不同,下文将会详细阐述。

#ifndef BIT_HACKS_H_

#define BIT_HACKS_H_

// Convert $bnumber to int.
// i.e. "101" to 5, "011" to 3 etc..
int strtob(char *bnumber);

// x is an even or odd number?
#define B_EVEN(x) ((((x) & 0x1) == 0) ? 1:0)
#define B_ODD(x) (!B_EVEN(x))

// n-th bit is set?
#define B_IS_SET(x, n) (((x) & (1<<(n))) ? 1:0)

// set or unset n-th bit.
#define B_SET(x, n) ((x) | (1<<(n)))
#define B_UNSET(x, n) ((x) & ~(1<<(n)))

// toggle n-th bit.
#define B_TOGGLE(x, n) ((x) ^ (1<<(n)))

// turn off rightmost 1-bit.
#define B_TURNOFF_1(x) ((x) & ((x)-1))

// turn on rightmost 0-bit.
#define B_TURNON_0(x) ((x) | ((x)+1))

// isolate rightmost 1-bit, i.e. set it, and unset the others.
#define B_ISOLATE_1(x) ((x) & ~((x)-1))

// isolate rightmost 0-bit.
#define B_ISOLATE_0(x) (~(x) & ((x)+1))

// propagate the rightmost 1, i.e. 2#00100 to 2#00111.
#define B_PROPAGATE_1(x) ((x) | ((x)-1))

#endif /* BIT_HACKS_H_ */

我这里的实现方式,同原文作者的实现方式不太一样。主要是两点。

  • 所有宏都没有「副作用」。原文作者的某些宏定义,对宏参数本身做了修改;而这里的每一个宏,只是一个纯粹的求值表达式,没有赋值过程。
  • 对于 B_ISOLATE_1(x) 的实现,原文作者使用了取负值的方法,不太容易理解,我这里使用了类似 B_TURNOFF_1(x) 的减一操作,个人觉得从比特位的角度来理解,更清晰一些。

作者在其后的一篇博客中,实现了这个头文件,并做了测试。然而其实现的一个部分,我不太赞同。即,如何很直观的从二进制数位表示法,生成一个十进制数?作者的方法如下。

#define HEXIFY(X) 0x##X##LU

#define B8IFY(Y) (((Y&0x0000000FLU)?1:0) + \
((Y&0x000000F0LU)?2:0) + \
((Y&0x00000F00LU)?4:0) + \
((Y&0x0000F000LU)?8:0) + \
((Y&0x000F0000LU)?16:0) + \
((Y&0x00F00000LU)?32:0) + \
((Y&0x0F000000LU)?64:0) + \
((Y&0xF0000000LU)?128:0))

#define B8(Z) ((unsigned char)B8IFY(HEXIFY(Z)))

这里的写法,是将诸如 10111100 的字符串当成一个 16 进制的无符号长整型处理,由 0x##X##LU 所完成。然后,再通过分别 B8IFY(Y) 宏完成十进制计算。

作者认为这是很机巧的一个做法,并对最初发明此写法的 Tom Torfs 表示感谢。然而我却觉得,这个写法比较糟糕。原因在于,长整型能够表示的数值范围大小有限,16 进制的 1 0 组合,未免过于浪费。正因如此,作者的例子都是在 B8 即 8 位比特范围内完成。然而,这里的 Bit Hacks 是通用的,只要数字以二进制补码形式表示,都能如是处理。

对于这个问题,我采取了字符串的方式,即上述头文件里的 int strtob(char *bnumber); 函数。实现也很简单,不再列出。

最后提一下作者另一个宏技巧,就是 # 符号,可以将某个宏参数变成字符串。值得借鉴。我利用这个特性写了个 run 宏。

// test_func : int (*test_func)(void);
#define run(test_func) \
do { \
int result = (test_func)(); \
if (result && verbosity == With_Function_Name) \
printf("Passed: %s\n", #test_func); \
else if (result) \
printf("."); \
} while (0)

- EOF -

原文地址:https://www.cnblogs.com/jtuki/p/toolbox_bit_hacks.html