简体   繁体   English

在 C 中使用按位运算的 2 位映射

[英]2-bit mapping using bitwise operations in C

This is my first question, so I hope to do this right.这是我的第一个问题,所以我希望做对。

I have a problem where I have to map a key which can be in the range (0, 1, 2) to select a value from the same range (0, 1, 2).我有一个问题,我必须映射一个可以在 (0, 1, 2) 范围内的键才能从相同的范围 (0, 1, 2) 中选择一个值。 I have to repeat this millions of times and I was trying to implement this by using bitwise operations in C, without success.我必须重复数百万次,我试图通过在 C 中使用按位运算来实现这一点,但没有成功。

So let's say I have 16 keys in the range (0, 1, 2) which I want to map to 16 values in the same range by using the following rules:因此,假设我在 (0, 1, 2) 范围内有 16 个键,我想使用以下规则将它们映射到同一范围内的 16 个值:

0 -> 2
1 -> 1
2 -> 1

I can represent the array of 16 keys as 16 2-bit pairs in a 32bit unsigned int.我可以将 16 个键的数组表示为 32 位无符号整数中的 16 个 2 位对。 For instance:例如:

  0, 1, 2, 1, 2, 0, ... //Original array of keys
 00 01 10 01 10 00 ...  //2-bit pairs representation of keys in a 32bit int

and I am interested in transforming the unsigned int, following the rules above (ie the 2-bit pairs have to be transformed following the rules: 00->10, 01->01, and 10->01), so that I end up with a 32bit unsigned int like:我对转换无符号整数感兴趣,遵循上述规则(即必须按照规则转换 2 位对:00->10、01->01 和 10->01),以便我结束使用 32 位无符号整数,如:

 10 01 01 01 01 10 ...  //2-bit pairs transformed using the given rule.

Would it be a relatively fast bitwise procedure which will allow me to apply efficiently this transformation (given that the transformation rules can change)?它是一个相对较快的按位过程,可以让我有效地应用这种转换(假设转换规则可以改变)?

I hope I formulated my question clearly.我希望我清楚地表达了我的问题。 Thanks for any help.谢谢你的帮助。

EDIT: I corrected some mistakes, and clarified some points following comments.编辑:我纠正了一些错误,并在评论后澄清了一些观点。

EDIT2: Following some suggestions, I add what I hope is a code example: EDIT2:根据一些建议,我添加了我希望的代码示例:

#include <stdio.h> 
#include <stdlib.h> 

int main(void) 
{ 
    int i;

    unsigned int keys[16];
    unsigned int bitKeys = 0;

    unsigned int mapping[3];

    unsigned int result[16];
    unsigned int bitResults = 0;

    //Initialize random keys and mapping dict    
    for(i = 0; i<16; i++) 
        keys[i] = rand() % 3;
        bitKeys |= keys[i] << (2*i);

    for(i = 0; i<3; i++) 
        mapping[i] = rand() % 3; 

    //Get results without using bitwise opperations.
    for(i = 0; i<16; i++) 
        result[i] = mapping[ keys[i] ];
        bitResults |= result[i] << (2*i);


    //Would it be possible to get bitResults directly from bitKeys efficiently by using bitwise operations?


    return 0; 
} 

This is essentially a problem of simplifying truth tables to minimal Boolean expressions;这本质上是将真值表简化为最小布尔表达式的问题; here we need two expressions, one for each output value bit.这里我们需要两个表达式,每个输出值位一个。

BA QP

00 10
01 01
10 01
11 XX

B: high key bit, A: low key bit, Q: high value bit, P: low value bit B:高值位,A:低值位,Q:高值位,P:低值位

By using any of the many tools available (including our brain) for minimizing combinational logic circuits, we get the expressions通过使用任何可用的工具(包括我们的大脑)来最小化组合逻辑电路,我们得到表达式

Q = ¬A·¬B
P = A + B

Now that we have the expressions, we can apply them to all keys in a 32-bit variable:现在我们有了表达式,我们可以将它们应用于 32 位变量中的所有键:

    uint32_t keys = 2<<30|0<<10|1<<8|2<<6|1<<4|2<<2|0;  // for example
    uint32_t vals = ~keys & ~keys<<1 & 0xAAAAAAAA   // value_high is !key_high & !key_low
                  | (keys>>1 | keys) & 0x55555555;  // value_low is key_high | key_low

I would need a solution for any arbitrary mapping.我需要任何任意映射的解决方案。

Here's an example program for arbitrary mappings.这是一个用于任意映射的示例程序。 For each of the two value bits, there are 2 3 possible expressions (the same set for both bits);对于两个值位中的每一个,都有 2 3 个可能的表达式(两个位的集合相同); these expressions are:这些表达是:

0    ¬A·¬B    A    ¬B    B    ¬A    A+B    1

By concatenating the high and low mapping bits, respectively, for keys 0, 1 and 2, we get the index of the expression corresponding to the mapping function.通过分别连接键 0、1 和 2 的高映射位和低映射位,我们得到与映射函数对应的表达式的索引。 In the following program, the values of all the expressions, even the ones unused by the mapping, are stored in the term array.在以下程序中,所有表达式的值,即使是映射未使用的值,都存储在term数组中。 While this may seem wasteful, it allows computation without branches, which may be a win in the end.虽然这看起来很浪费,但它允许没有分支的计算,这最终可能是一个胜利。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main()
{
    int i;
    unsigned mapping[3];
    // generate example mapping
    for (i = 0; i < 3; ++i) mapping[i] = rand() % 3, printf(" %d->%d", i, mapping[i]);
    puts("");

    // determine the mapping expression index 0..7 for high and low value bit
    short h = mapping[0]/2 | mapping[1]/2<<1 | mapping[2]/2<<2;
    short l = mapping[0]%2 | mapping[1]%2<<1 | mapping[2]%2<<2;

    uint32_t keys = 0x1245689A; // for example

    uint32_t b = keys, a = keys<<1;
    uint32_t term[8] = { 0, ~a&~b, a, ~b, b, ~a, a|b, -1 };  // all possible terms
    uint32_t vals = term[h]    & 0xAAAAAAAA   // value_high
                  | term[l]>>1 & 0x55555555;  // value_low
    printf("%8x\n%8x\n", keys, vals);
}

After thinking about it, and using some of the ideas from other answers, I think I found a general solution.经过思考,并使用了其他答案中的一些想法,我想我找到了一个通用的解决方案。 It is based in first estimating the value assuming there are only the keys 10, and 01 (ie one bit of the pair determines the other) and then correct by the key 00. An example code of the solution:它基于首先估计值,假设只有密钥 10 和 01(即一对中的一个位确定另一个),然后通过密钥 00 进行更正。解决方案的示例代码:

#include <stdio.h> 
#include <stdlib.h> 

void printBits(size_t const size, void const * const ptr)
{
    unsigned char *b = (unsigned char*) ptr;
    unsigned char byte;
    int i, j;

    for (i=size-1;i>=0;i--)
    {
        for (j=7;j>=0;j--)
        {
            byte = (b[i] >> j) & 1;
            printf("%u", byte);
            if(j%2 == 0) printf("|");
        }
    }
    puts("");
}

int test2BitMapping(unsigned int * mapping)
{
    int i;

    unsigned int keys[16];
    unsigned int bitKeys = 0;

    unsigned int b = 0;
    unsigned int c = 0;
    unsigned int d = 0;
    unsigned int expand[4] = {0x00000000u, 0x55555555u, 0xAAAAAAAAu, 0xFFFFFFFFu};
    unsigned int v12 = 0;
    unsigned int v0mask = 0;

    unsigned int result[16];
    unsigned int bitResults = 0;
    unsigned int bitResultsTest = 0;

    //Create mapping masks
    b = ((1 & mapping[1]) | (2 & mapping[2]));
    c = (2 & mapping[1]) | (1 & mapping[2]);
    d = mapping[0];

    b = expand[b];
    c = expand[c];
    d = expand[d];

    //Initialize random keys
    for(i = 0; i<16; i++) {
        if(0) { //Test random keys
            keys[i] = rand() % 3;
        }
        else { //Check all keys are generated
            keys[i] = i % 3;
        }
        bitKeys |= keys[i] << (2*i);
    }

    //Get results without using bitwise opperations.
    for(i = 0; i<16; i++) {
        result[i] = mapping[ keys[i] ];
        bitResultsTest |= result[i] << (2*i);
    }

    //Get results by using bitwise opperations.
    v12 = ( bitKeys & b ) | ( (~bitKeys) & c );
    v0mask = bitKeys | (((bitKeys & 0xAAAAAAAAu) >> 1) | ((bitKeys & 0x55555555u) << 1));
    bitResults = ( d & (~v0mask) ) | ( v12 & v0mask );


    //Check results
    if(0) {
        for(i = 0; i<3; i++) {
            printf("%d -> %d, ", i, mapping[i]); 
        }
        printf("\n"); 
        printBits(sizeof(unsigned int), &bitKeys);
        printBits(sizeof(unsigned int), &bitResults);
        printBits(sizeof(unsigned int), &bitResultsTest);
        printf("-------\n");
    }
    if(bitResults != bitResultsTest) {
        printf("*********\nDifferent\n*********\n");
    }
    else {
        printf("OK\n");
    }
}

int main(void) 
{ 
    int i, j, k;
    unsigned int mapping[3];

    //Test using random mapping
    for(k = 0; k < 1000; k++) {
        for(i = 0; i<3; i++) {
            mapping[i] = rand() % 3; 
        }
        test2BitMapping(mapping);
    }

    //Test all possible mappings
    for(i = 0; i<3; i++) {
        for(j = 0; j<3; j++) {
            for(k = 0; k<3; k++) {
                mapping[0] = i;
                mapping[1] = j;
                mapping[2] = k; 

                test2BitMapping(mapping);
            }
        }
    }


    return 0; 
} 

and I am interested in transforming the unsigned int, following the rules above (ie the 2-bit pairs have to be transformed following the rules: 00->10, 01->01, and 10->01), so that I end up with a 32bit unsigned int我对转换无符号整数感兴趣,遵循上述规则(即必须按照规则转换 2 位对:00->10、01->01 和 10->01),以便我结束使用 32 位无符号整数

Certainly this can be done, but the required sequence of operations will be different for each of the 27 distinct mappings from { 0, 1, 2 } to { 0, 1, 2 }.这当然可以做到,但是对于从 { 0, 1, 2 } 到 { 0, 1, 2 } 的 27 个不同映射中的每一个,所需的操作序列将是不同的。 Some can be very simple, such as for the three constant mappings, but others require more complex expressions.有些可能非常简单,例如三个常量映射,但有些则需要更复杂的表达式。

Without having performed a thorough analysis, I'm inclined to guess that the mappings that are neither constant nor permutations, such as the one presented in the example, probably have the greatest minimum complexity.在没有进行彻底分析的情况下,我倾向于猜测既不是常数也不是排列的映射,例如示例中的映射,可能具有最大的最小复杂度。 These all share the characteristic that two keys map to the same value, whereas the other key maps to a different one.这些都具有以下特征:两个键映射到相同的值,而另一个键映射到不同的值。 One way -- not necessarily the best -- to approach finding an expression for such a mapping is to focus first on achieving the general result that the two keys map to one value and the other to a different one, and then move on to transforming the resulting values to the desired ones, if necessary.一种方法——不一定是最好的——为这样的映射找到一个表达式是首先专注于实现两个键映射到一个值而另一个映射到另一个值的一般结果,然后继续转换如有必要,将结果值转换为所需的值。

For the example presented, for instance,例如,对于呈现的示例,

 0 -> 2 1 -> 1 2 -> 1

, one could (on a per-key basis) use ((key & 2) >> 1) | ((key & 1) << 1) ,可以(在每个键的基础上)使用((key & 2) >> 1) | ((key & 1) << 1) ((key & 2) >> 1) | ((key & 1) << 1) to achieve these preliminary results: ((key & 2) >> 1) | ((key & 1) << 1)实现这些初步结果:

0 -> 0
1 -> 3
2 -> 3

, which can be converted to the desired final result by flipping the higher-order bit via an exclusive-or operation. ,可以通过异或运算翻转高位,将其转换为所需的最终结果。

Note well the bit masking.请注意位掩码。 There are other ways that could be approached for mapping a single key, but for the case of multiple keys stored in contiguous bits of the same integer, you need to be careful to avoid contaminating the computed mappings with data from different keys.还有其他方法可以用于映射单个键,但是对于存储在同一整数的连续位中的多个键的情况,您需要小心避免使用来自不同键的数据污染计算的映射。

In 16-entry bit-vector form, that would be在 16 项位向量形式中,这将是

uint32_t keys = /*...*/;
uint32_t values = (((keys & 0xAAAAAAAAu) >> 1) | ((keys & 0x55555555u) << 1))
        ^ 0xAAAAAAAAu;

. . That happens to have a couple fewer operations than the expression in your other answer so far, but I am not certain that it is the smallest possible number of operations.到目前为止,这恰好比您的其他答案中的表达式少了几个操作,但我不确定它是否是最小的操作次数。 In fact, if you are prepared to accept arithmetic operations in addition to bitwise ones, then you can definitely do it with fewer operations:事实上,如果你准备接受除按位运算之外的算术运算,那么你绝对可以用更少的运算来完成:

uint32_t keys = /*...*/;
uint32_t values = 0xAAAAAAAAu
        - (((keys & 0xAAAAAAAAu) >> 1) | (keys & 0x55555555u));

Of course, in general, various operations do not all have the same cost as each other, but integer addition and subtraction and bitwise AND, OR, and XOR all have the the same cost as each other on most architectures (see, for example, https://www.agner.org/optimize/instruction_tables.pdf ).当然,一般来说,各种运算的代价并不都相同,但整数加减法和按位与、或、异或在大多数架构上都具有相同的代价(参见,例如, https://www.agner.org/optimize/instruction_tables.pdf )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM