【问题标题】:Detect 0xff in multibyte word with bitwise operations使用按位运算检测多字节字中的 0xff
【发布时间】:2018-04-25 16:36:20
【问题描述】:

我有一个由 4 个字节组成的 32 位无符号整数,例如:0x12ff3456

我正在尝试找到一个按位运算,将0xff 字节替换为0x01,并用0x00 替换其他任何内容,例如:

0x12ff3456 => 0x00010000

0x543245ff => 0x00000001 ...等

一次只有一个构成 32 位无符号整数的字节可以等于 0xff。 有没有人知道如何以尽可能少的操作来执行此操作?折叠(bitshifts + ands)是一种选择,但需要太多的操作。

【问题讨论】:

    标签: bit-manipulation bitwise-operators bit-shift bitwise-and


    【解决方案1】:

    存在多种可能性,哪一种会产生最少的指令数或最快的执行时间,取决于所使用的机器架构和编译器。一些架构提供ANDN指令,其他架构支持三输入逻辑指令,还有一些架构将移位与逻辑运算合并。下面我展示了三个通过详尽测试的变体。

    这两种方法要么将输出基于0xFF 的逐字节相等检查,要么基于0xFE 的逐字节“大于”测试。这些是通过FUNC_VARIANT 选择的。 “大于”测试进一步基于“小于”测试,提供了两种实现变体,由LTU_VARIANT 选择。

    在 cmets 中注明了智能字节处理算法的来源。一般来说,在中间步骤中需要进行一定量的屏蔽,以防止对特定字节的处理影响到相邻字节。

    请注意,代码可以很容易地适应一次处理八个字节,而不是问题指定的四个。

    Compiler Explorer 的快速检查表明,使用 gcc,FUNC_VARIANT=1,LTU_VARIANT=0 编译为 x86-64ARM64 的最短指令序列。不过,这不一定能转化为尽可能高的性能。

    #include <stdlib.h>
    #include <stdio.h>
    #include <stdint.h>
    
    #define FUNC_VARIANT  0
    #define LTU_VARIANT   0
    
    #define UINT32_H4  0x80808080U   // byte-wise sign bits (MSBs)
    #define UINT32_L4  0x01010101U   // byte-wise LSBs 
    #define UINT32_M4  0xffffffffU   // byte-wise maximum
    
    uint32_t sign_to_bool4 (uint32_t a)
    {
        return (a >> 7) & UINT32_L4;
    }
    
    uint32_t vhaddu4 (uint32_t a, uint32_t b)
    {
        /* Peter L. Montgomery's observation (newsgroup comp.arch, 2000/02/11,
           https://groups.google.com/d/msg/comp.arch/gXFuGZtZKag/_5yrz2zDbe4J):
           (A+B)/2 = (A AND B) + (A XOR B)/2.
        */
        return (a & b) + (((a ^ b) >> 1) & ~UINT32_H4);
    }
    
    uint32_t ltu4_core (uint32_t a, uint32_t b)
    {
        /* Sebastiano Vigna, "Broadword implementation of rank/select queries." 
           In: International Workshop on Experimental and Efficient Algorithms, 
           pp. 154-168, Springer Berlin Heidelberg, 2008.
        */
        return (((a | UINT32_H4) - (b & ~UINT32_H4)) | (a ^ b)) ^ (a | ~b);
    }
    
    uint32_t vsetltu4 (uint32_t a, uint32_t b)
    {
    #if LTU_VARIANT==1
        return sign_to_bool4 (ltu4_core (a, b));
    #else // LTU_VARIANT
        return sign_to_bool4 (vhaddu4 (~a, b));
    #endif // LTU_VARIANT
    }
    
    uint32_t vsetgtu4 (uint32_t a, uint32_t b)
    {
        return vsetltu4 (b, a);
    }
    
    uint32_t vseteq4 (uint32_t a, uint32_t b)
    {
        uint32_t r, t;
    
        /* Alan Mycroft's null-byte detection algorithm (newsgroup comp.lang.c, 1987/04/08,
           https://groups.google.com/forum/#!original/comp.lang.c/2HtQXvg7iKc/xOJeipH6KLMJ):
           null_byte(x) = ((x - 0x01010101) & (~x & 0x80808080))
        */
        r = a ^ b;         // 0x00 if a == b
        t = r | UINT32_H4; // set msbs, to catch carry out
        r = r ^ t;         // extract msbs, msb = 1 if r < 0x80
        t = t - UINT32_L4; // sign bit = 0, if r was 0x00 or 0x80
        t = r & ~t;        // sign_bit = 1, if r was 0x00
        r = t >> 7;        // convert to bool
        return r;
    }
    
    uint32_t func (uint32_t a) 
    {
    #if FUNC_VARIANT == 1
        return vsetgtu4 (a, ~UINT32_L4); // byte-wise a >ᶸ 0xFE
    #else // FUNC_VARIANT
        return vseteq4 (a, UINT32_M4);   // byte-wise a == 0xFF
    #endif // FUNC_VARIANT
    }
    
    uint32_t ref_func (uint32_t a)
    {
        uint8_t a0 = (uint8_t)((a >>  0) & 0xff);
        uint8_t a1 = (uint8_t)((a >>  8) & 0xff);
        uint8_t a2 = (uint8_t)((a >> 16) & 0xff);
        uint8_t a3 = (uint8_t)((a >> 24) & 0xff);
        int p0 = (a0 == 0xff);
        int p1 = (a1 == 0xff);
        int p2 = (a2 == 0xff);
        int p3 = (a3 == 0xff);
        return (((uint32_t)p3 << 24) | ((uint32_t)p2 << 16) |
                ((uint32_t)p1 <<  8) | ((uint32_t)p0 <<  0));
    }
    
    int main (void)
    {
        uint32_t res, ref, x = 0;
    
        do {
            res = func (x);
            ref = ref_func (x);
            if (res != ref) {
                printf ("error @ %08x: res=%08x  ref=%08x\n", x, res, ref);
                return EXIT_FAILURE;
            }
            x++;
        } while (x);
        printf ("test passed\n");
        return EXIT_SUCCESS;
    }
    

    【讨论】:

    • 感谢您提供详尽的回答!经过进一步测试,简单的if/else 分支虽然比按位解决方案快,但它们的组合指令总体上必须更慢。
    【解决方案2】:

    Bit Twiddling Hacks 页面解释了如何获得一个掩码,其中每个零字节都标有高位集。您可以在此处应用:

    ((~x - 0x01010101) & x & 0x80808080) >> 7
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-18
      • 1970-01-01
      • 2015-05-07
      相关资源
      最近更新 更多