位:搜索和替换位序列

Bits: Search&Replace bit sequences

我自己是一名高级程序员,我在位运算上遇到了很多困难。我希望我努力实现的目标是可行的?

假设我有一个无符号 8 位整数 - 它可以是任何值。让我们研究两个例子:0xe4 (11100100) 和 0xa5 (10100101).

硬件将这些解释为 4 :11 10 01 00 和 10 10 01 01。

我正在尝试用纯 C 编写 3 个方法:

试图搜索位替换方法,但无法解决这个特殊要求。我应该从哪里开始?

unsigned replace2Bits(unsigned haystack, unsigned search, unsigned replace, unsigned numchunks)
{
    unsigned mask = 3;
    while(numchunks--)
    {
        if((haystack & mask) == search)
        {
            haystack &= ~mask;
            haystack |= replace;
        }
        mask <<= 2;
        search <<= 2;
        replace <<= 2;
    }
    return haystack;
}

void printbin(unsigned x)
{
    for(unsigned bit = 0x80; bit; bit >>= 1)
    {
        putchar((x & bit) ? '1':'0' );
    }
}

int main()
{
    unsigned char hs1 = 0xe4;
    unsigned char hs2 = 0xa5;

    printbin(hs1);putchar(' ');printbin(hs1 = replace2Bits(hs1,0b00, 0b01, 4)); putchar(' ');
    putchar(' ');printbin(hs1 = replace2Bits(hs1,0b01, 0b10, 4)); putchar(' ');
    putchar(' ');printbin(replace2Bits(hs1,0b10, 0b11, 4)); putchar('\n');

    printbin(hs2);putchar(' ');printbin(hs2 = replace2Bits(hs2,0b00, 0b01, 4)); putchar(' ');
    putchar(' ');printbin(hs2 = replace2Bits(hs2,0b01, 0b10, 4)); putchar(' ');
    putchar(' ');printbin(replace2Bits(hs2,0b10, 0b11, 4)); putchar('\n');
}

https://godbolt.org/z/dcsuuD

这里有一些快速、非循环的单行代码,可以满足您的需求:

unsigned set_00_to_01(unsigned x) {
    return x + ((~x >> 1) & ~x & 0x55);
}

unsigned set_01_to_10(unsigned x) {
    return x + ((~x >> 1) & x & 0x55);
}

unsigned set_10_to_11(unsigned x) {
    return x + ((x >> 1) & ~x & 0x55);
}

请注意,它们仅对低 8 位进行操作,但可以通过将 0x55 值更改为 0x55550x55555555 来轻松更改它们以对更多位进行操作实例.

每个函数都硬连接到其特定任务,因此您不能传入任意值。他们的主要优势是速度。

首先,我假设您不打算在单个操作中连续执行这些操作。如果你真的这样做了,不管输入是什么,结果总是 0xff,所以你不需要处理输入,把它分解成字段,或者其他任何事情。所以我假设如果输入中的一对位以 00 开始,那么该字段的最终结果应该是 01,同样如果它是 01,它应该以 10 结束,如果它是 10 它应该最终为 11(稍后,您可能会再次调用它,以便每个都进行下一步,依此类推)。

其次,我要指出,这些操作与简单地递增该 2 位字段相同。即:0->1、1->2、2->3(还有 3,我们大概不用管它,尽管您没有指定)。

获取 C 数据类型中的一些位的一种简单方法是使用其位域支持。在这种情况下,我们将它与一个联合一起使用,因此我们有一个联合成员可以访问各个字段,另一个成员可以访问整个字节作为一个单元。

#include <stdio.h>

// depending on how new it is, your compiler may already define this:
typedef unsigned char uint8_t;

struct Foo {
    uint8_t a : 2;
    uint8_t b : 2;
    uint8_t c : 2;
    uint8_t d : 2;
};

typedef union { 
    unsigned char whole;
    struct Foo parts;
} val;

现在我们可以访问这些片段了,让我们编写一些代码来演示如何使用它:

int main(void) {
    val a = {.whole = 0xe4};

    // replacing 00 with 01, 01 with 10 and 10 with 11 is incrementing, except for 11
    if (a.parts.a < 3)
        ++a.parts.a;
    if (a.parts.b < 3)
        ++a.parts.b;
    if (a.parts.c < 3)
        ++a.parts.c;
    if (a.parts.d < 3)
        ++a.parts.d;

    printf("%2.2x\n", a.whole);
}

警告:理论上,写入联合的一个字段,然后从另一个字段读取不会给出明确的结果。然而,实际上,与硬件接口的代码相当常规地做这样的事情。不同的是排序——(例如)a.parts.aa.whole 中的最低有效位还是更高有效位。不幸的是,这不仅会因平台而异,而且在同一平台上的不同编译器之间也会有所不同。另一方面,在你的情况下,这根本无关紧要。