使用按位运算符在一个int中打包多个值
低级别位操作从来都不是我的强项。 我将理解一些有助于理解以下bitwise运算符的用例。考虑…
int age, gender, height, packed_info; . . . // Assign values // Pack as AAAAAAA G HHHHHHH using shifts and "or" packed_info = (age << 8) | (gender <> 7) & 1; age = (packed_info >> 8);
我不确定这段代码是完成的以及如何完成? 为什么要使用幻数0x7F? 如何完成包装和拆包?
资源
正如评论所说,我们将把年龄,性别和身高打包成15位格式:
AAAAAAAGHHHHHHH
让我们从这部分开始:
(age << 8)
首先,年龄有这种格式:
age = 00000000AAAAAAA
其中每个A可以是0或1。
<< 8
将位移动到左侧8位,并用零填充间隙。 所以你得到:
(age << 8) = AAAAAAA00000000
同理:
gender = 00000000000000G (gender << 7) = 0000000G0000000 height = 00000000HHHHHHH
现在我们想将这些组合成一个变量。 |
运算符通过查看每个位来工作,如果任一输入中的位为1,则返回1。 所以:
0011 | 0101 = 0111
如果一个输入中的位为0,则从另一个输入获得该位。 看(age << 8)
, (gender << 7)
和height
,你会看到,如果其中一个的位为1,则其他的为0。 所以:
packed_info = (age << 8) | (gender << 7) | height = AAAAAAAGHHHHHHH
现在我们想解开这些比特。 让我们从高度开始吧。 我们想得到最后7位,并忽略前8位。为此,我们使用&
运算符,只有当两个输入位都是1时才返回1.所以:
0011 & 0101 = 0001
所以:
packed_info = AAAAAAAGHHHHHHH 0x7F = 000000001111111 (packed_info & 0x7F) = 00000000HHHHHHH = height
为了达到这个年龄,我们可以将所有8个位置向右推,我们留下了0000000AAAAAAAA
。 所以age = (packed_info >> 8)
。
最后,为了获得性别,我们将所有7个位置推到右边以摆脱高度。 然后我们只关心最后一点:
packed_info = AAAAAAAGHHHHHHH (packed_info >> 7) = 0000000AAAAAAAG 1 = 000000000000001 (packed_info >> 7) & 1 = 00000000000000G
这可能是一个相当长的位操作课程,但首先让我指出维基百科上的位屏蔽文章 。
packed_info = (age << 8) | (gender << 7) | height;
取年龄并将其值移动超过8位,然后取性别并将其移动超过7位,高度将占据最后一位。
age = 0b101 gender = 0b1 height = 0b1100 packed_info = 0b10100000000 | 0b00010000000 | 0b00000001100 /* which is */ packed_info = 0b10110001100
解包反过来但使用像0x7F(0b 01111111)这样的掩码来修剪字段中的其他值。
gender = (packed_info >> 7) & 1;
会像...一样工作
gender = 0b1011 /* shifted 7 here but still has age on the other side */ & 0b0001 /* which is */ gender = 0b1
注意,将任何内容与1进行AND运算与“保持”该位相同,而使用0进行AND运算与“忽略”该位相同。
如果您要将日期存储为数字,也许您可以通过将年份乘以10000,将月份乘以100并添加日期来完成此操作。 2011年7月2日等日期将编码为20110702:
year * 10000 + month * 100 + day -> yyyymmdd 2011 * 10000 + 7 * 100 + 2 -> 20110702
我们可以说我们用yyyymmdd蒙版编码了日期。 我们可以将此操作描述为
- 将第4年的职位转移到左边,
- 将月份2位置向左移动
- 按原样离开。
- 然后将三个值组合在一起。
这与年龄,性别和身高编码相同,只是作者正在思考二进制。
查看这些值可能具有的范围:
age: 0 to 127 years gender: M or F height: 0 to 127 inches
如果我们将这些值转换为二进制,我们会这样:
age: 0 to 1111111b (7 binary digits, or bits) gender: 0 or 1 (1 bit) height: 0 to 1111111b (7 bits also)
考虑到这一点,我们可以使用掩码aaaaaaahhhhhhh对年龄 – 性别 – 身高数据进行编码,这里我们只讨论二进制数字,而不是十进制数字。
所以,
- 将年龄8 位向左移,
- 将性别7 位向左移动并且
- 保持高度不变。
- 然后将所有三个值组合在一起。
在二进制中,Shift-Left运算符(<<)向左移动值n个位置。 “Or”运算符(许多语言中的“|”)将值组合在一起。 因此:
(age << 8) | (gender << 7) | height
现在,如何“解码”这些值?
二进制比十进制更容易:
- 你“掩盖”高度,
- 将性别7位向右移动并将其掩盖掉,最后
- 将年龄8位向右移动。
Shift-Right运算符(>>)将值向右移动n个位置(丢失最右侧位置“向外”移位的数字)。 “And”二元运算符(许多语言中的“&”)掩码位。 为此,它需要一个掩码,指示要保留哪些位以及要销毁哪些位(保留1位)。 因此:
height = value & 1111111b (preserve the 7 rightmost bits) gender = (value >> 1) & 1 (preserve just one bit) age = (value >> 8)
由于hex中的1111111b在大多数语言中都是0x7f,这就是这个神奇数字的原因。 使用127(十进制为1111111b)可以产生相同的效果。
一个更简洁的答案:
AAAAAAA G HHHHHHH
填料:
packed = age << 8 | gender << 7 | height
或者,如果在MySQL SUM聚合函数中使用,则可以对组件求和
packed = age << 8 + gender << 7 + height
开箱:
age = packed >> 8 // no mask required gender = packed >> 7 & ((1 << 1) - 1) // applying mask (for gender it is just 1) height = packed & ((1 << 7) - 1) // applying mask
另一个(更长)的例子:
假设您有一个要打包的IP地址,但它是一个虚构的IP地址,例如132.513.151.319。 请注意,某些组件大于256,与实际IP地址不同,需要8位以上。
首先,我们需要弄清楚我们需要使用什么偏移才能存储最大数量。 假设我们虚构的IP没有任何组件可以大于999,这意味着每个组件需要10位存储(允许数字高达1014)。
packed = (comp1 << 0 * 10) | (comp1 << 1 * 10) | (comp1 << 2 * 10) | (comp1 << 3 * 10)
其中给出了dec 342682502276
或bin 100111111001001011110000000010010000100
现在让我们解压缩值
comp1 = (packed >> 0 * 10) & ((1 << 10) - 1) // 132 comp2 = (packed >> 1 * 10) & ((1 << 10) - 1) // 513 comp3 = (packed >> 2 * 10) & ((1 << 10) - 1) // 151 comp4 = (packed >> 3 * 10) & ((1 << 10) - 1) // 319
其中(1 << 10) - 1
是一个二进制掩码,我们用来隐藏左边的位超出我们感兴趣的10个最右边的位。
使用MySQL查询的相同示例
SELECT (@offset := 10) AS `No of bits required for each component`, (@packed := (132 << 0 * @offset) | (513 << 1 * @offset) | (151 << 2 * @offset) | (319 << 3 * @offset)) AS `Packed value (132.513.151.319)`, BIN(@packed) AS `Packed value (bin)`, (@packed >> 0 * @offset) & ((1 << @offset) - 1) `Component 1`, (@packed >> 1 * @offset) & ((1 << @offset) - 1) `Component 2`, (@packed >> 2 * @offset) & ((1 << @offset) - 1) `Component 3`, (@packed >> 3 * @offset) & ((1 << @offset) - 1) `Component 4`;
左移运算符意味着“乘以这两次”。 在二进制中,将数字乘以2与向右侧添加零相同。
右移运算符与左移运算符相反。
管道运算符是“或”,意味着将两个二进制数叠加在一起,并且在任一个数字中都有1,该列中的结果为1。
那么,让我们解压出packed_info的操作:
// Create age, shifted left 8 times: // AAAAAAA00000000 age_shifted = age << 8; // Create gender, shifted left 7 times: // 0000000G0000000 gender_shifted = gender << 7; // "Or" them all together: // AAAAAAA00000000 // 0000000G0000000 // 00000000HHHHHHH // --------------- // AAAAAAAGHHHHHHH packed_info = age_shifted | gender_shifted | height;
拆包是相反的。
// Grab the lowest 7 bits: // AAAAAAAGHHHHHHH & // 000000001111111 = // 00000000HHHHHHH height = packed_info & 0x7F; // right shift the 'height' bits into the bit bucket, and grab the lowest 1 bit: // AAAAAAAGHHHHHHH // >> 7 // 0000000AAAAAAAG & // 000000000000001 = // 00000000000000G gender = (packed_info >> 7) & 1; // right shift the 'height' and 'gender' bits into the bit bucket, and grab the result: // AAAAAAAGHHHHHHH // >> 8 // 00000000AAAAAAA age = (packed_info >> 8);
您可以将表达式x & mask
视为从x
中删除x & mask
中不存在的位(即,值为0)的操作。 这意味着, packed_info & 0x7F
从packed_info
删除高于第七位的所有位。
示例:如果packed_info
是二进制的1110010100101010
,那么packed_info & 0x7f
将是
1110010100101010 0000000001111111 ---------------- 0000000000101010
因此,在height
我们得到packed_info
的低7位。
接下来,我们将整个packed_info
移动7,这样我们就删除了已经读出的信息。 所以我们得到(对于前面例子中的值) 111001010
性别存储在下一位,所以使用相同的技巧: & 1
我们只从信息中提取该位。 其余信息包含在第8个偏移处。
打包也不复杂:你把它1110010100000000
8位(所以你从11100101
得到1110010100000000
),将gender
移7(所以得到00000000
),然后取高度(假设它适合低7位) 。 然后,你将所有这些组合在一起:
1110010100000000 0000000000000000 0000000000101010 ---------------- 1110010100101010
我曾经多次遇到同样的要求。 借助Bitwise AND运算符非常容易。 只需增加两(2)的幂即可使您的价值合格。 要存储多个值,请添加它们的相对数(2的幂)并获得SUM。 此SUM将合并您选择的值。 怎么样 ?
只需对每个值进行按位AND,对于未选择的值,将给出零(0),为其选择非零。
这是解释:
1)价值观(是,否,可能)
2)两个权力的分配(2)
YES = 2^0 = 1 = 00000001 NO = 2^1 = 2 = 00000010 MAYBE = 2^2 = 4 = 00000100
3)我选择YES和MAYBE因此SUM:
SUM = 1 + 4 = 5 SUM = 00000001 + 00000100 = 00000101
该值将存储YES和MAYBE。 怎么样?
1 & 5 = 1 ( non zero ) 2 & 5 = 0 ( zero ) 4 & 5 = 4 ( non zero )
因此SUM包括
1 = 2^0 = YES 4 = 2^2 = MAYBE.
有关更详细的说明和实施,请访问我的博客