4.2 位域值提取
位域定义
C语言中的位域定义:
struct Bits{
uint32_t b2:2;
uint32_t b4:4;
};
提取位域定义的值算法如下:
符号定义:
- w :定义的位宽,如 b2 的位宽为 2 bit;
- p :位域字段的位偏移,如 b2 的位偏移为 0,b4 的位偏移为 2;
- v :位域字段所在的整体数据值,如 b2 就在 uint32_t 的数据上;
- r :计算的结果;
第1步:计算位域掩码
mask = (1 << w) - 1
第2步:逻辑右移
tv = v >> p
第3步:截取域至
v = tv & mask
总结
r = (v >> p) & ( (1 << w) - 1)
如果v是有符号数,就需要进行符号扩展。
符号扩展
第1步:计算符号掩码
smask = 1 << (w-1)
第2步:计算符号位值
signed = r & smask
第3步:符号扩展
signed != 0 ? ( r = r | (-1 « w) ) : r
总结
( (1 « (w-1)) & r) != 0 ? ( r = r | (-1 « w) ) : r
4.3 INT_MAX和INT_MIN
假设
假设您有一个 32 位系统:
- INT_MAX是 01111111111111111111111111111111 ;
- INT_MIN是 10000000000000000000000000000000 ;
0 和 1 分别位于最高有效位位置,分别表示符号位。
计算INT_MAX和INT_MIN 在 C/C++ 中:
数字 0 表示为 000…000(32个)。
原理
我们计算 0 的 NOT 得到一个有 32 个 1 的数字。这个数字不等于INT_MAX因为符号位是1,即负数。
现在,这个数字的右移将产生011…111 这是INT_MAX。
将INT_MAX 按位取反 就得到INT_MIN。
代码
unsigned int max = 0;
max = ~max;
max = max >> 1;
int min = ~max;
4.4 应用 n + (~n) = -1
原理
设整数 n 类型为 int_8,值为 3,则 3 + (~3) = 0000 0011 + 1111 1100 = 1111 1111 = -1,所以引出非运算的基础公式 n + (~n) = -1,也可以将 n ^ -1 = ~n 带入。
位运算实现n+1与n-1
对n + (~n) = -1进行等式变换可得:
int n;
~n = -(n + 1);
n + 1 = -~n;
n - 1 = ~-n; // 假设n = -n,可推出此等式
取相反数
一个数的相反数等于其按位取反后再加1,对上等式变换推出:
取绝对值
这一块内容也用到了n ^ 0 = n 和 n ^ -1 = ~n:
- 若 n 为非负数,则 n » 31 = 0,所以 abs = n ^ 0 - 0 = n。
- 若 n 为负数,则 n » 31 = -1,所以 abs = n ^ (-1) + 1 = ~n + 1 = -1 - n + 1 = -n。
int abs, n;
abs = (n ^ (n >> 31)) - (n >> 31)
4.5 应用 n ^ n = 0 和 n ^ 0 = n
交换两个数的值
不需要第三个临时变量,交换两个数的值
int a, b;
a ^= b; // a = a ^ b;
b ^= a; // b = b ^ a = b ^ a ^ b = (b ^ b) ^ a = 0 ^ a = a
a ^= b; // a = a ^ b = a ^ a ^ b = 0 ^ b = b
代替特定的条件赋值
如果 x = a,则 a ^ b ^ x = 0 ^ b;如果 x = b,则 a ^ b ^ x = 0 ^ a;
所以下列代码可等价于:x = a ^ b ^ x。
int a, b, x;
if(x == a)
x = b;
else if(x == b)
x = a;
// 上面代码等价于
x = a ^ b ^ x
4.6 应用 x&(x-1)
原理
x&(x-1)可以消除数字x二进制表示的最后一个1,如:
int x = 0xf6;
printf("%x\n", x); //0b11110110
printf("%x\n", x&(x-1)); //0b11110100
判断一个正数是不是2的次幂
如果一个正数是2的次幂,则这个数的二进制表示中只含有一个1。
int x;
if(x&(x-1)){
//x至少含有两个1,所以不是2的次幂
}
计算一个数的二进制含有多少个1
x中的最后一个1可以通过操作x = x&(x-1)循环消去,当最后x值为0时,便可以求出二进制中1的个数。
int x, total;
while(x > 0){
x = x&(x-1);
total++;
}
4.7 应用 2的次方
整数对2的乘/除法
整数 n 向右移一位,相当于将 n 除以 2;数 n 向左移一位,相当于将 n 乘以 2。
int n = 2;
n >> 1; // 1
n << 1; // 4
n 对“2的次方”取余
m 是2的次方,则其二进制数只有一个1,如 4 => 0100,8 => 1000。m-1 之后,原本 m 二进制的1变成0,原本1后面的0全变成1,如 4-1 = 3 => 0011,8-1 = 7 => 0111。
2 ^ 0 = 1
2 ^ 1 = 2
2 ^ 2 = 4
2^ 3 = 8
2 ^ 4 = 16
2 ^ 5 = 32
…
可以看出 2^(q + 1) 永远都是 2^q 的整数倍,而 2^q 比 2^(q - 1)+2^(q - 2) … + 2^0 的和还要大 1。
假设 m = 2^q ,q 为正整数。n 的二进制数中,第[ n 的最高位, q ]位的和是 m 的整数倍,而第[ q-1, 0 ]位的和是 n/m 的余数,也就是说将n的二进制数的第[ q-1, 0 ]位截取,即可得到 n/m 的余数。
m = 2^q , m − 1 = 2^(q - 1)+2^(q - 2) … + 2^0 => 00011…1([ q-1, 0 ]位全为1),所以 n & (m - 1) 的值为 n/m 的余数。
int mod, n, m; // m是2的次方,如4,8等
mod = n & (m - 1);
将 n 以“2的次方”倍数最小补全
有 n 和 m 两数,m 为2的次方,找到大于等于 n 且正好是 m 整数倍的最小数。
假设 m = 2^q ,则 m 的倍数的二进制数中,第 q 位为1,其余位全为 0;(m - 1) 的二进制数中[ q-1, 0 ]位全为 1,其余位全为 0。
所以有两种情况:
- 如果n的二进制数中[ q-1, 0 ]位全为 0,则 n 就是 m 整数倍的最小数;
- 如果n的二进制数中[ q-1, 0 ]位不全为 0,在第 q 位上加 1,所得结果就是 m 整数倍的最小数。
现给 n 加上一个[ q-1, 0 ]位全为 1,其余位全为 0 的数( m-1 就是这个数),并将所得结果的[ q-1, 0 ]位全部置为 0(结果与 ~(m - 1) 相与即可),便可满足这两种情况。
int min, n, m;
min = (n + m - 1) & ~(m - 1);
4.9 循环移位
循环左移
以32位整数为例,循环左移的过程可以分为3步:
- 将 x 左端的 n 位先移动到 y 的低 n 位中,x » (32-n);
- 将 x 左移 n 位,其右面低位补 0,x«n;
- 进行按位或运算(x » (32 - n) | (x « n));
循环右移
以32位整数为例,循环右移的过程可以分为3步:
- 将 x 的左端的低 n 位先移动到 y 的高 n 位中 x « (32 - n)
- 将 x 右移 n 位,其左面高 n 位补 0,x » n;
- 进行按位或操作(x « (32 - n) | (x » n));
总结
假如将一个无符号的数据 val ,长度为 N bit,需要循环移动 n bit。可以利用下面的公式:
循环左移:(val >> (N - n) | (val << n))
循环右移:(val << (N - n) | (val >> n))
- 注:n ->[0,N-1],所以 在运算之前,将 n 映射到该范围: n = n & ( N - 1 )
实现
//循环左移
unsigned int ROL(unsigned int val,unsigned int n){
n&=31;
return (val >> (32 - n) | (val << n));
}
//循环右移
unsigned int ROL(unsigned int val,unsigned int n){
n&=31;
return (val << (32 - n) | (val >> n));
}