使用按位运算符在一个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 342682502276bin 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 & 0x7Fpacked_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. 

有关更详细的说明和实施,请访问我的博客

Interesting Posts