39

低级位操作从来都不是我的强项。对于理解以下按位运算符的用例,我将不胜感激。考虑...

int age, gender, height, packed_info;

. . .   // Assign values 

// Pack as AAAAAAA G HHHHHHH using shifts and "or"
packed_info = (age << 8) | (gender << 7) | height;

// Unpack with shifts and masking using "and"
height = packed_info & 0x7F;   // This constant is binary ...01111111
gender = (packed_info >> 7) & 1;
age    = (packed_info >> 8);

我不确定这段代码正在完成什么以及如何完成?为什么使用幻数 0x7F ?装箱和拆箱是如何完成的?

来源

4

7 回答 7

80

正如评论所说,我们将把年龄、性别和身高打包成 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),您会看到,如果其中一个位为 1,则其他位为 0。所以:(gender << 7)height

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
于 2011-07-02T12:37:41.583 回答
13

这可能是关于位操作的一个相当长的课程,但首先让我向您指出Wikipedia 上的位掩码文章

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 与“保留”该位相同,与 0 与“忽略”该位相同。

于 2011-07-02T12:33:57.167 回答
7

如果您要将日期存储为数字,也许您可​​以通过将年份乘以 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)

考虑到这一点,我们可以使用掩码aaaaaaaghhhhhhh对年龄-性别-身高数据进行编码,只是这里我们讨论的是二进制数字,而不是十进制数字。

所以,

  • 将年龄左移 8
  • 将性别向左移动 7位,然后
  • 保持高度不变。
  • 然后将所有三个值组合在一起。

在二进制中,左移运算符 (<<) 将值向左移动n 个位置。“或”运算符(许多语言中的“|”)将值组合在一起。所以:

    (age << 8) | (gender << 7) | height

现在,如何“解码”这些值?

二进制比十进制更容易:

  • 你“掩盖”了高度,
  • 将性别向右移动 7 位并将其屏蔽掉,最后
  • 将年龄向右移动 8 位。

Shift-Right 运算符 (>>) 将值向右移动 n 个位置(从最右边位置“移出”的任何数字都将丢失)。“与”二元运算符(许多语言中的“&”)屏蔽位。为此,它需要一个掩码,指示要保留哪些位以及要销毁哪些位(保留 1 个位)。所以:

    height = value & 1111111b (preserve the 7 rightmost bits)
    gender = (value >> 1) & 1 (preserve just one bit)
    age = (value >> 8)

由于十六进制的 1111111b 在大多数语言中是 0x7f,这就是那个幻数的原因。通过使用 127(十进制为 1111111b),您将获得相同的效果。

于 2011-07-02T14:01:19.930 回答
3

更简洁的答案:

啊啊啊啊啊啊啊啊啊啊

包装:

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 的组件需要超过 8 位,这与真实 IP 地址不同。

首先,我们需要弄清楚我们需要使用什么偏移量才能存储最大数量。假设我们虚构的 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`;
于 2014-07-17T23:38:53.667 回答
2

左移运算符的意思是“乘以 2,这么多次”。在二进制中,将数字乘以 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);
于 2011-07-02T12:40:26.123 回答
2

我曾多次面临同样的要求。在按位与运算符的帮助下,这很容易。只需通过增加二 (2) 的幂来限定您的值。要存储多个值,请将它们的相对数(2 的幂)相加并得到总和。此 SUM 将合并您选择的值。如何 ?

只需对每个值进行按位与运算,对于未选择的值和已选择的非零值,它将给出零 (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.

更详细的解释和实现请访问我的博客

于 2018-01-19T16:08:04.047 回答
1

您可以将表达式视为从中不存在(即值为 0)的位中x & mask删除的​​操作。这意味着,从第七位以上的所有位中删除。xmaskpacked_info & 0x7Fpacked_info

示例:如果packed_info1110010100101010二进制,那么packed_info & 0x7f将是

1110010100101010
0000000001111111
----------------
0000000000101010

所以,height我们得到了 的低 7 位packed_info

接下来,我们将整体移动packed_info7,这样我们就删除了我们已经读出的信息。所以我们得到(对于上一个例子中的值)111001010性别存储在下一位,所以使用相同的技巧:& 1我们只从信息中提取那个位。其余信息包含在偏移量 8 中。

打包回来也不复杂:你取age,将它移动 8 位(所以你1110010100000000从得到11100101),移动gender7 (所以你得到00000000),然后取高度(假设它适合较低的 7 位)。然后,您将所有这些组合在一起:

1110010100000000
0000000000000000
0000000000101010
----------------
1110010100101010
于 2011-07-02T12:34:34.213 回答