根据标志是等于1还是0,将标志转换为0xFF或0

时间:2017-01-30 19:33:47

标签: bit-manipulation

我有一个二进制标志f,等于零或一。 如果等于1,我想转换为0xFF,否则转换为0。

当前的解决方案是f*0xFF,但我宁愿使用bit twiddling来实现这一目标。

2 个答案:

答案 0 :(得分:2)

如何:

(unsigned char)-f

或者:

0xFF & -f

如果f已经是char,那么您只需要-f

这种方法之所以有效,是因为-0 == 0-1 == 0xFFFFF...,所以如果f大于{{1},可能会直接设置否定,或者设置一些额外的高位(你没有说)。

请记住,编译器很聪明。我尝试了以下所有解决方案,并且所有编译都减少到3个或更少的指令,并且没有一个分支(甚至是带有条件的解决方案):

条件

char

编译为:

int remap_cond(int f) {
  return f ? 0xFF : 0;
}

所以即使是"显而易见的"条件在三个指令中运行良好,在大多数现代x86硬件上有2或3个周期的延迟,具体取决于remap_cond: test edi, edi mov eax, 255 cmove eax, edi ret 性能。

乘法

您的原始解决方案:

cmov

实际上编译成漂亮的代码,完全避免乘法,用移位和减法代替它:

int remap_mul(int f) {
  return f * 0xFF;
}

这通常需要在具有mov-elimination的机器上进行两个循环,并且remap_mul: mov eax, edi sal eax, 8 sub eax, edi ret 通常会通过内联删除。

减法

正如 corn3lius 指出的那样,你可以从mov和面具做一些减法,如下所示:

0x100

这将编译为 1

int remap_shift_sub(int f) {
  return 0xFF & (0x100 - f);
}
到目前为止,我认为这是最好的 - 大多数主机上的延迟为2个周期,remap_shift_sub: neg edi movzx eax, dil ret 通常可以通过内联 2 来消除 - 例如,它可以在随后的消费指令中使用8位寄存器。

请注意,编译器巧妙地消除了屏蔽操作(您可能会争论movzx帐户),并使用movzx常量,因为它理解简单的否定确实同样的事情(特别是,0x100-f之间的所有位都被0x100 - f操作掩盖了。

直接导致以下C代码:

0xFF & ...

编译完全相同的东西。

你可以play with all of this on godbolt

1 除了int remap_neg_mask(int f) { return -f; } 之外,它会在clang中插入一个额外的mov来获取结果,而不是首先在那里生成结果。

2 请注意,通过" inlining"我的意思是,如果您实际将此函数写为函数,那么编译器会真正内联,但如果您只是在没有函数的情况下直接在需要它的位置执行重映射操作,会发生什么。

答案 1 :(得分:1)

value = 0xFF & ((1 << 16) - f )

如果f为1,则从0x100中扣除0xFF;否则用0xFF减去0和位掩码并得到0

太明显了?

value = ( f == 1 ) ? 0xFF : 0;