【问题标题】:use of the bitwise operators to pack multiple values in one int使用按位运算符将多个值打包到一个 int 中
【发布时间】:2011-09-27 06:28:29
【问题描述】:

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

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 ?打包和拆包是如何完成的?

Source

【问题讨论】:

  • 我认为在问这个问题之前,值得阅读有关二进制数表示和按位运算符的知识。
  • 评论中的图片几乎说明了一切:AAAAAAA G HHHHHHH

标签: java bitwise-operators packing bit-packing


【解决方案1】:

我曾多次遇到相同的要求。在按位与运算符的帮助下,这很容易。只需通过增加二 (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.

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

【讨论】:

    【解决方案2】:

    更简洁的答案:

    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 的组件需要超过 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 &lt;&lt; 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`;
    

    【讨论】:

      【解决方案3】:

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

      右移运算符 (>>) 将值向右移动 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)也会有同样的效果。

      【讨论】:

      • 感谢您提供的详细信息。它真的很有用。
      【解决方案4】:

      左移运算符的意思是“乘以 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);
      

      【讨论】:

        【解决方案5】:

        正如评论所说,我们要将年龄、性别和身高打包成 15 位,格式为:

        AAAAAAAGHHHHHHH
        

        让我们从这部分开始:

        (age << 8)
        

        首先,年龄的格式如下:

        age           = 00000000AAAAAAA
        

        其中每个 A 可以是 0 或 1。

        &lt;&lt; 8 将位向左移动 8 位,并用零填充空白。所以你得到:

        (age << 8)    = AAAAAAA00000000
        

        同样:

        gender        = 00000000000000G
        (gender << 7) = 0000000G0000000
        height        = 00000000HHHHHHH
        

        现在我们想将这些组合成一个变量。 | 运算符的工作原理是查看每个位,如果任一输入中的位为 1,则返回 1。所以:

        0011 | 0101 = 0111
        

        如果一个输入中的某个位为 0,则您从另一个输入中获取该位。查看(age &lt;&lt; 8)(gender &lt;&lt; 7)height,您会看到,如果其中一个位为1,则其他位为0。所以:

        packed_info = (age << 8) | (gender << 7) | height = AAAAAAAGHHHHHHH
        

        现在我们要解包这些位。让我们从高度开始。我们想要获取最后 7 位,而忽略前 8 位。为此,我们使用 &amp; 运算符,它仅在两个输入位都为 1 时才返回 1。所以:

        0011 & 0101 = 0001
        

        所以:

        packed_info          = AAAAAAAGHHHHHHH
        0x7F                 = 000000001111111
        (packed_info & 0x7F) = 00000000HHHHHHH = height
        

        要获得年龄,我们可以将所有内容向右推 8 位,剩下的是0000000AAAAAAAA。所以age = (packed_info &gt;&gt; 8)

        最后,为了获得性别,我们将所有内容向右推 7 个位置以摆脱高度。然后我们只关心最后一点:

        packed_info            = AAAAAAAGHHHHHHH
        (packed_info >> 7)     = 0000000AAAAAAAG
        1                      = 000000000000001
        (packed_info >> 7) & 1 = 00000000000000G
        

        【讨论】:

        • 这是一篇非常好的文章。在我读过的所有内容中,这是第一个说明正在发生的事情。
        【解决方案6】:

        您可以将表达式x &amp; mask 视为从x 中删除mask 中不存在(即值为0)的位的操作。这意味着,packed_info &amp; 0x7F 会从 packed_info 中删除所有高于第七位的位。

        例如:如果packed_info 在二进制中是1110010100101010,那么packed_info &amp; 0x7f 将是

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

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

        接下来,我们将整个packed_info 移动7,这样我们就可以删除我们已经读出的信息。所以我们得到(对于前面示例中的值)111001010 性别存储在下一位,所以使用相同的技巧:&amp; 1 我们只从信息中提取该位。其余信息包含在偏移量 8 处。

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

        1110010100000000
        0000000000000000
        0000000000101010
        ----------------
        1110010100101010
        

        【讨论】:

          【解决方案7】:

          这可能是关于位操作的一个相当长的课程,但首先让我向您指出bit masking article on 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 与“忽略”该位相同。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-01-24
            • 2011-07-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-09-11
            • 1970-01-01
            相关资源
            最近更新 更多