C位旋转使int 0变为1,而任何non-zero变为-1? (理想情况下是 x86 固有的)

C bit twiddling so that int 0 becomes 1, while any non-zero becomes -1? (ideally x86 intrinsic)

我正在寻找一种方法来实现我在标题中所写的内容。 我现在用“如果”来做,我想摆脱分支。 我已经查看了几页,例如 this one,找不到我要找的确切内容。

如果没有位操作,你可以这样做:

int func(int x)
{
    return int(uint(x-1) / uint(-1)) * 2 - 1;
}

或者:

int func(int x)
{
    return int(uint(~x) / uint(~0)) * 2 - 1;
}

x 转换为布尔值不会在当前 x86 处理器上生成任何分支。您可以使用简单的算法来生成结果:

int test_zero(int x) {
    return 1 - 2 * !!x;
}

gcc 11.2 生成这个:

test_zero:
    cmp     edi, 1
    sbb     eax, eax
    and     eax, 2
    sub     eax, 1
    ret

clang 13.0.0 生成这个:

test_zero:                              # @test_zero
    xor     eax, eax
    test    edi, edi
    sete    al
    add     eax, eax
    add     eax, -1
    ret

正如 dratenik 评论的那样,更简单、更易读的源代码编译成完全相同的无分支可执行代码:

int test_zero2(int x) {
    return x ? -1 : 1;
}

您检查 Godbolt's compiler explorer 上的代码生成。

clang/gcc 输出(来自 chqrlie 的回答)可以被截断为

cmp  edi, 1
sbb  eax, eax
or   eax, 1

sbb eax, eax 之后 eax == 0 edi != 0。 但由于 -1 和 1 都设置了 LSB,我们可以这样设置。

唉,即使我们可以为

生成两个指令序列
int test_zero_3(int x) {
   return x ? -1 : 0;
}
...
neg     edi
sbb     eax, eax
 

我们无法欺骗 clang,但我们可以让 gcc 产生预期的(或接近等效的)序列

int test_zero_4(int x) {
   return (test_zero_3(x)) | 1;
}
...
neg     edi
sbb     eax, eax
or      eax, 1