根据第二个的值,位破解到 return 两个值之一
Bit hack to return one of two values depending on the value of the second
假设 x
是位掩码(即除 1 之外的所有位均为 0)并且 y
是位掩码或等于 0。我需要一点破解return x
如果 y
为非零,如果 y
为零,则 return 为零。
这是一种可能的解决方案:取 x
和 y
的以 2 为底的对数(使用 de Bruijn 序列)并将它们相减,将值存储在 d
中。然后 y << d
将 return x
除非 y
一开始是零。
这种方法有两个问题:1) 如果 y
为零,从技术上讲,以 2 为底的对数是未定义的。不确定这是否重要,因为即使 d
是一些垃圾值,如果 y
为零,y << d
仍应 return 为零; 2) 如果 d
是负数,右移运算符不会变成左移运算符(根据 Google 搜索),这意味着我必须包括一些符号检查。
我相信有更简单的方法,但我找不到它,希望能得到一些帮助。
编辑:澄清一下,我正在寻找执行此操作的最快方法。显而易见的 if (y == 0) return 0; else return x
使用 if
语句,因此受到分支预测的不利影响,这就是为什么我要求助于复杂的 base-2 日志解决方案。
static_cast<bool>(y) * x
只要取 y 并使用它的位形成一个全 1 的字符串(如果它不为零),然后将其与 x 相运算。这样做的愚蠢方法是线性的,但你也可以用二进制方法来做(未给出)。
#include <stdio.h>
#include <limits.h>
int foo(int x, int y) {
int z = 0;
for(int z = 1; z < CHAR_BIT * sizeof(int); z ++) {
y |= y << z;
}
return x & y;
}
int main() {
printf("%lx\n", foo(0x1000, 0xdead));
return 0;
}
在常数时间内应该 运行。你当然可以展开循环。
在大多数常见的处理器架构上,最好使用三元运算符:
/* if y != 0, return x, else return 0 */
int select1 (int x, int y)
{
return y ? x : 0;
}
三元运算符的使用通常不涉及在现代处理器体系结构上使用分支,因为它可以通过使用条件移动(例如在 x86 上)、指令预测(例如在 ARM 上)以无分支方式轻松实现,或 select 指令(例如在某些 GPU 上)。
如果不希望或不允许使用三元运算符,并且需要一种复杂的解决方案,则可以(假设平台对整数使用二进制补码表示)使用:
/* if y != 0, return x, else return 0 */
int select2 (int x, int y)
{
return (0 - (y != 0)) & x;
}
请注意 select2()
可能 比 select1()
慢 。示例:如果我为 x86-64 架构编译上述函数,我的编译器会为 select1()
生成此指令序列
test edx, edx
cmovne edx, ecx
mov eax, edx
ret
但是 select2()
的这个较长的指令序列:
mov r8d, 1
test edx, edx
cmovne edx, r8d
neg edx
and edx, ecx
mov eax, edx
ret
请注意,这两个指令序列都没有将分支作为值 selection 的一部分,但是 select2()
中的指令序列需要执行更多的指令,并且具有更长的依赖链,与select1()
.
中的指令序列
假设 x
是位掩码(即除 1 之外的所有位均为 0)并且 y
是位掩码或等于 0。我需要一点破解return x
如果 y
为非零,如果 y
为零,则 return 为零。
这是一种可能的解决方案:取 x
和 y
的以 2 为底的对数(使用 de Bruijn 序列)并将它们相减,将值存储在 d
中。然后 y << d
将 return x
除非 y
一开始是零。
这种方法有两个问题:1) 如果 y
为零,从技术上讲,以 2 为底的对数是未定义的。不确定这是否重要,因为即使 d
是一些垃圾值,如果 y
为零,y << d
仍应 return 为零; 2) 如果 d
是负数,右移运算符不会变成左移运算符(根据 Google 搜索),这意味着我必须包括一些符号检查。
我相信有更简单的方法,但我找不到它,希望能得到一些帮助。
编辑:澄清一下,我正在寻找执行此操作的最快方法。显而易见的 if (y == 0) return 0; else return x
使用 if
语句,因此受到分支预测的不利影响,这就是为什么我要求助于复杂的 base-2 日志解决方案。
static_cast<bool>(y) * x
只要取 y 并使用它的位形成一个全 1 的字符串(如果它不为零),然后将其与 x 相运算。这样做的愚蠢方法是线性的,但你也可以用二进制方法来做(未给出)。
#include <stdio.h>
#include <limits.h>
int foo(int x, int y) {
int z = 0;
for(int z = 1; z < CHAR_BIT * sizeof(int); z ++) {
y |= y << z;
}
return x & y;
}
int main() {
printf("%lx\n", foo(0x1000, 0xdead));
return 0;
}
在常数时间内应该 运行。你当然可以展开循环。
在大多数常见的处理器架构上,最好使用三元运算符:
/* if y != 0, return x, else return 0 */
int select1 (int x, int y)
{
return y ? x : 0;
}
三元运算符的使用通常不涉及在现代处理器体系结构上使用分支,因为它可以通过使用条件移动(例如在 x86 上)、指令预测(例如在 ARM 上)以无分支方式轻松实现,或 select 指令(例如在某些 GPU 上)。
如果不希望或不允许使用三元运算符,并且需要一种复杂的解决方案,则可以(假设平台对整数使用二进制补码表示)使用:
/* if y != 0, return x, else return 0 */
int select2 (int x, int y)
{
return (0 - (y != 0)) & x;
}
请注意 select2()
可能 比 select1()
慢 。示例:如果我为 x86-64 架构编译上述函数,我的编译器会为 select1()
test edx, edx
cmovne edx, ecx
mov eax, edx
ret
但是 select2()
的这个较长的指令序列:
mov r8d, 1
test edx, edx
cmovne edx, r8d
neg edx
and edx, ecx
mov eax, edx
ret
请注意,这两个指令序列都没有将分支作为值 selection 的一部分,但是 select2()
中的指令序列需要执行更多的指令,并且具有更长的依赖链,与select1()
.