将 false/true 转换为 -1/+1 的无分支方法?

Branchless method to convert false/true to -1/+1?

做以下映射的无分支方式是什么?

true -> +1
false -> -1

一个简单的方法是 if b then 1 else -1 但我正在寻找一种避免分支的方法,即如果。

如果相关,我正在使用 Python。

您可以利用 Python 中的类型 bool 是数字这一事实:

>>> True == 1
True
>>> False == 0
True

所以表达式 2 * b - 1 给出了想要的结果:

>>> def without_branching(b):
...     return 2 * b - 1
... 
>>> without_branching(True)
1
>>> without_branching(False)
-1

然而,这是否真的是有争议的"branchless"。它将被编译为 Python 没有条件跳转的字节码,但字节码解释器肯定会做一些条件跳转以执行它:至少,它必须检查要执行的操作码,操作数的类型*- 有,依此类推。

也许我们可以像这样使用列表:

[None, True, False][1]
# output True

[None, True, False][-1]
# output False

更新:与评论中提到的相反方式:

[-1, 1][int(False)]
# output -1

[-1, 1][int(True)]
# output 1

更新:或者更简单的使用元组而不需要 int() 转换(正如评论中提到的那样):

(-1, 1)[False]
# output -1

(-1, 1)[True]
# output 1

这里是目前评论和答案中发布的解决方案的比较。

我们可以使用dis模块来查看每种情况下生成的字节码;这证实了没有条件跳转指令(至少在 Python 代码本身),并且还告诉我们一些关于预期性能的信息,因为执行的操作码数量对此有直接影响(尽管它们是不完全相关)。函数调用的数量也与性能相关,因为它们的开销特别高。

@Glannis Clipper 和@kaya3:(-1, 1)[b](3 个操作码)

  1           0 LOAD_CONST               2 ((-1, 1))
              3 LOAD_NAME                0 (b)
              6 BINARY_SUBSCR

@HeapOverflow:-(-1)**b(4 个操作码)

  1           0 LOAD_CONST               0 (-1)
              2 LOAD_NAME                0 (b)
              4 BINARY_POWER
              6 UNARY_NEGATIVE

@HeapOverflow:b - (not b)(4 个操作码)

  1           0 LOAD_NAME                0 (b)
              2 LOAD_NAME                0 (b)
              4 UNARY_NOT
              6 BINARY_SUBTRACT

@kaya3:2 * b - 1(5 个操作码)

  1           0 LOAD_CONST               0 (2)
              3 LOAD_NAME                0 (b)
              6 BINARY_MULTIPLY
              7 LOAD_CONST               1 (1)
             10 BINARY_SUBTRACT

@HeapOverflow:~b ^ -b(5 个操作码)

  1           0 LOAD_NAME                0 (b)
              2 UNARY_INVERT
              4 LOAD_NAME                0 (b)
              6 UNARY_NEGATIVE
              8 BINARY_XOR

@Mark Meyer:b - (b - 1) * -1(7 个操作码)

  1           0 LOAD_NAME                0 (b)
              3 LOAD_NAME                0 (b)
              6 LOAD_CONST               0 (1)
              9 BINARY_SUBTRACT
             10 LOAD_CONST               1 (-1)
             13 BINARY_MULTIPLY
             14 BINARY_SUBTRACT

@Sayse:{True: 1, False: -1}[b](7 个操作码)

  1           0 LOAD_CONST               0 (True)
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (False)
              9 LOAD_CONST               3 (-1)
             12 BUILD_MAP                2
             15 LOAD_NAME                0 (b)
             18 BINARY_SUBSCR

@deceze:{True: 1}.get(b, -1)(7 个操作码,1 个函数调用)

  1           0 LOAD_CONST               0 (True)
              3 LOAD_CONST               1 (1)
              6 BUILD_MAP                1
              9 LOAD_ATTR                0 (get)
             12 LOAD_NAME                1 (b)
             15 LOAD_CONST               2 (-1)
             18 CALL_FUNCTION            2 (2 positional, 0 keyword pair)

@Glannis Clipper:[-1, 1][int(b)](7 个操作码,1 个函数调用)

  1           0 LOAD_CONST               1 (-1)
              3 LOAD_CONST               0 (1)
              6 BUILD_LIST               2
              9 LOAD_NAME                0 (int)
             12 LOAD_NAME                1 (b)
             15 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             18 BINARY_SUBSCR

@divyang4481:2 * int(b) - 1(7 个操作码,1 个函数调用)

  1           0 LOAD_CONST               0 (2)
              3 LOAD_NAME                0 (int)
              6 LOAD_NAME                1 (b)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 BINARY_MULTIPLY
             13 LOAD_CONST               1 (1)
             16 BINARY_SUBTRACT