在C语言中如何用一次操作交换两个位?

12

假设我有一个六位未知值的字节:

???1?0??

我想交换第2位和第4位的比特位(不改变任何?的值):

???0?1??

但我如何在C语言中用一次操作来完成这个操作呢?

我正在微控制器上每秒执行数千次此操作,因此性能是最重要的。

"切换"这些位将是可行的。尽管这不同于交换位,但对于我的目的来说,切换也可以正常工作。


3
哇,三个不同的数字,但回答完全相同 - 祝你好运! - Benjol
1
你是想交换这两个位,还是切换这两个位?也就是说,00 变成 00 还是变成 11? - Adam Rosenfield
3
他说“交换”,他们都做了“切换” :) - Benjol
3
如果这里的意图是交换两个比特值,就像交换一样,那么下面的答案都是不正确的。 - Andre Miller
3
如果你正在使用已知的微控制器,那么最好研究汇编指令,而不是用C语言。 - Matthew Monaghan
显示剩余9条评论
8个回答

31

尝试:

x ^= 0x14;

那会切换两个位。问题中有点不太清楚,因为你首先提到了交换,然后给出了一个切换的例子。无论如何,要交换这两个位:

x = precomputed_lookup [x];

如果 precomputed_lookup 是一个 256 字节的数组,那么这可能是最快的方法,这取决于内存速度相对于处理器速度的比率。否则,就是:

x = (x & ~0x14) | ((x & 0x10) >> 2) | ((x & 0x04) << 2);

编辑:关于位的翻转,还有一些信息。

当你使用异或运算符(^)把两个整数值相互异或时,异或运算将在位级别上执行,就像这样:

for each (bit in value 1 and value 2)
   result bit = value 1 bit xor value 2 bit

目的是让第一个值的第0位和第二个值的第0位做异或运算,第1位和第二个值的第1位以此类推。异或操作不会影响值中的其他位。实际上,它是在多个位上进行了并行位异或。

观察异或的真值表,你会发现用'1'对一个位进行异或操作可以有效地切换该位的状态。

 a  b a^b
 0  0  0
 0  1  1
 1  0  1
 1  1  0

因此,要切换位1和3,请写一个二进制数,在您想要切换位的位置放置1,在您想要保留值不变的位置放置0:

00001010

转换为十六进制:0x0a。您可以切换任意数量的位:

0x39 = 00111001

将切换位0、3、4和5


不知道为什么这个被踩了,它和其他答案一样正确。 - Skurmedel
假设 OP 想要切换两个位(而不是交换两个位),这就是正确的答案。OP 使用的约定是 LSB 是位 0。 - Adam Rosenfield
实际上,这是更正确的,因为它是唯一在正确位上操作的答案。仍然只是切换状态而不是交换 - Nietzche-jou
Skizz,你能解释一下为什么 x ^= 0x14; 可以同时切换两个位吗?例如,如何计算什么十六进制值可以切换第1位和第3位的位? - Nate Murray
1
@Nate:将一个位与1进行异或操作会切换该位。将一个位与0进行异或操作会保持不变。如果您想要切换32位值中的某个特定位集,请创建另一个32位值,其中所需切换的位设置为1,其余为0。对于第2位和第4位,(1 << 2) | (1 << 4) == 0x14。对于第1位和第3位,(1 << 1) && (1 << 3) == 0xA。 - Matt J
仅供记录,您将显式操作写成一行并不意味着它是“一个操作”,正如OP所要求的那样。如果计算所有移位、或和与操作,这一行C代码由8个单独的位操作组成。如果你正在优化操作数,使用查找表可能会更好。另一方面,一个内存访问可能比多个逻辑操作慢得多。到最后,最好实现两者并进行性能测试,看哪个在您的系统中效果最好。 - Nathan Fellman

11

使用位操作符无法在单个指令中“交换”两个比特(即交换它们的位置,而不是值)。

如果您真的想要交换它们,最佳方法可能是使用查找表。对于许多“棘手”的转换,这一点也适用。

BYTE lookup[256] = {/* left this to your imagination */};

for (/*all my data values */) 
  newValue = lookup[oldValue];

如果您可以节省256字节,并且内存访问比位运算更快,则这是最佳选择。这取决于系统。例如,在现代x86处理器上,如果查找表在缓存中,则查找非常快,但如果没有,则在执行查找所需的时间内可能可以执行几百个位运算。在小型嵌入式控制器中,您可能没有256字节可用。 - Nathan Fellman

7
以下方法不是单个C指令,而只是另一种位运算方法。该方法简化自使用XOR交换单个位
Roddy的回答所述,查找表可能是最好的选择。我只建议在您不想使用查找表时使用此方法。这确实会交换位,而不仅是切换(也就是说,第2位中的任何内容都将在第4位中,并反之亦然)。
b: 您的原始值-???1?0??,例如 x: 临时变量 r:结果
x = ((b >> 2) ^ (b >> 4)) & 0x01 r = b ^ ((x << 2) | (x << 4))
快速解释:获取您想查看的两个位并对它们进行XOR运算,将该值存储到x中。通过将此值向后移动到位2和4(并进行OR运算),您可以获得一个掩码,当与b进行XOR运算时,将交换您的两个原始位。下表显示了所有可能的情况。
bit2: 0 1 0 1  
bit4: 0 0 1 1  
x   : 0 1 1 0   <-- Low bit of x only in this case 
r2  : 0 0 1 1  
r4  : 0 1 0 1

我没有完全测试过这个,但是对于我快速尝试的几种情况,它似乎有效。

2
这是一种非常优雅的方法。基本上,您正在说如果位不同(如果它们不同,则x为1),则翻转位(有效地交换它们),否则保持不变(这与交换它们相同,因为它们是相同的)。 - Nathan Fellman

4
这可能不是最优化的,但应该可以工作:
unsigned char bit_swap(unsigned char n, unsigned char pos1, unsigned char pos2)
{
    unsigned char mask1 = 0x01 << pos1;
    unsigned char mask2 = 0x01 << pos2;
   if ( !((n & mask1) != (n & mask2)) )
        n ^= (mask1 | mask2);
    return n;
}

2
这是不正确的——(n & mask1) != (n & mask2) 将进行整数比较,但你想要进行布尔比较。将其修改为"if (bool(n & mask1) != bool(n & mask2))"即可使其正确。 - arolson101
我认为问题是关于“交换两个位”而不是“切换位”。不是吗? - vinod maverick

2
下面的函数将交换第2位和第4位。如果需要预先计算查找表(以便交换成为单个操作),可以使用此函数:
unsigned char swap24(unsigned char bytein) {
    unsigned char mask2 = ( bytein & 0x04 ) << 2;
    unsigned char mask4 = ( bytein & 0x10 ) >> 2;
    unsigned char mask  = mask2 | mask4 ;
    return ( bytein & 0xeb ) | mask;
}

我将每个操作都写在不同的行上,以使其更加清晰易懂。

1
void swap_bits(uint32_t& n, int a, int b) {
    bool r = (n & (1 << a)) != 0;
    bool s = (n & (1 << b)) != 0;

    if(r != s) {
        if(r) {
            n |= (1 << b);
            n &= ~(1 << a);
        }
        else {
            n &= ~(1 << b);
            n |= (1 << a);
        }
    }
}

n是你想要交换的整数,ab是你想要交换的位的位置(索引),从最不重要的位开始计数,从零开始。

使用你的示例(n = ???1?0??),你将如下调用函数:

swap_bits(n, 2, 4);
原理: 只有当位不同时才需要交换它们的位(这就是为什么 r != s)。在这种情况下,其中一个为1,另一个为0。之后,只需注意您想要执行一次位设置操作和一次位清除操作。

0

假设你的值是x,即x=???1?0??

这个操作可以切换这两个位:

x = x ^ ((1<<2) | (1<<4));

0
#include<stdio.h>

void printb(char x) {
    int i;
    for(i =7;i>=0;i--) 
        printf("%d",(1 & (x >> i)));
    printf("\n");
}

int swapb(char c, int p, int q) {
    if( !((c & (1 << p)) >> p) ^ ((c & (1 << q)) >> q) )
        printf("bits are not same will not be swaped\n");
    else {
        c = c ^ (1 << p);
        c = c ^ (1 << q);
    }
    return c;
}

int main() 
{
    char c = 10;
    printb(c);
    c = swapb(c, 3, 1);
    printb(c);
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接