您最喜欢的按位技术是什么?[关闭]


14

几天前,StackExchange成员Anto询问了按位运算符的有效用法我说过,移位比将整数除以2的幂更快。StackExchange成员Daemin反驳说,右移带来了负数问题。

那时,我还没有真正考虑过将带符号整数用于移位运算符。我主要在底层软件开发中使用了该技术。因此,我总是使用无符号整数。C对无符号整数执行逻辑移位。执行逻辑右移时,无需注意符号位。空位用零填充。但是,当右移有符号整数时,C执行算术移位运算。空位用符号位填充。此差异导致负值四舍五入为无穷大而不是被舍弃为零,这与有符号整数除法不同。

经过几分钟的思考,得出了第一手的解决方案。该解决方案在移位之前有条件地将负值转换为正值。在执行移位操作后,值将有条件地转换回其负数形式。

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

该解决方案的问题在于,通常将条件赋值语句转换为至少一个跳转指令,并且在不对两个指令路径都进行解码的处理器上,跳转指令可能会非常昂贵。必须两次重新灌注指令流水线才能很好地降低通过分频获得的任何性能提升。

综上所述,我在周六醒来,回答了条件分配问题。我们在执行算术移位运算时遇到的舍入问题仅在使用二进制补码表示形式时发生。它不会以补码表示出现。该问题的解决方案包括在执行移位操作之前将二进制补码值转换为一个二进制补码值。然后,我们必须将一个人的补码值转换回一个两个人的补码值。令人惊讶的是,我们可以执行这组操作,而无需在执行移位操作之前有条件地转换负值。

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

通过减去1将2的补码负值转换为1的补码负值。另一方面,一个补码负值通过加一转换为一个二进制补码负值。上面列出的代码之所以有效,是因为符号位用于将二进制补码转换为二进制补码,反之亦然。只有负值会设置其符号位;因此,当a为正时,变量符号将等于零。

综上所述,您能想到上面提到的其他一些按位技巧吗?您最喜欢的按位黑客技术是什么?我一直在寻找面向性能的新型按位黑客。


3
这个问题和您的帐户名-这个世界再次变得有意义...
JK

+1有趣的问题作为我的后续活动;)
Anto

我也曾经做过一些快速的奇偶校验计算。奇偶校验有点麻烦,因为传统上它涉及循环和计数是否设置了位,所有这些都需要大量的跳转。可以使用shift和XOR来计算奇偶校验,然后一堆又一堆地执行,可以避免循环和跳转。
quick_now 2011年

2
您是否知道关于这些技术的整本书?
-Hackers

是的,还有一个专门用于位操作的网站。我忘记了该网址,但Google会尽快将其打开。
quick_now 2011年

Answers:


23

我喜欢Gosper的破解程序(HAKMEM#175),这是一种非常聪明的方法,可以获取一个数字并使用设置的相同位数获得下一个数字。例如,在k从中生成项目组合时很有用n

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1。但是从现在开始,我将在调试过程中加注释地进行噩梦。
nikie 2011年

@ nikie,muahahahaha!(我倾向于将其用于诸如Euler项目问题​​之类的工作-我的日常工作并不涉及很多组合运算)。
彼得·泰勒

7

高速逆平方根方法使用最离奇位级技术来计算,我见过一个平方根的倒数:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

快速sqrt也很棒。Carmack似乎是最伟大的编码器之一。
本杰明·B

Wikipedia甚至有更老的资料来源,例如beyond3d.com/content/articles/15
MSalters 2011年

0

除以3-不求助于运行时库调用。

事实证明,除以3(感谢堆栈溢出的提示)可以近似为:

X / 3 = [(x / 4)+(x / 12)]

X / 12是(x / 4)/3。这里突然出现了递归元素。

事实证明,如果您限制所玩数字的范围,则可以限制所需的迭代次数。

因此,对于<2000的无符号整数,以下是一种快速简单的/ 3算法。(对于更大的数字,只需添加更多步骤)。编译器对此进行了优化,因此最终变得又快又小:

静态无符号short FastDivide3(const无符号short arg)
{
  无符号的简短RunningSum;
  无符号短小数的第十二位;

  RunningSum = arg >> 2;

  FractionalTwelth = RunningSum >> 2;
  RunningSum + =分数第十二;

  分数十二>> = 2;
  RunningSum + =分数第十二;

  分数十二>> = 2;
  RunningSum + =分数第十二;

  分数十二>> = 2;
  RunningSum + =分数第十二;

  //重复上述两行,以获得更高的精度

  返回RunningSum;
}

1
当然,这仅适用于非常模糊的微控制器。最近二十年来制造的任何实际CPU都不需要用于整数除法的运行时库。
MSalters 2011年

1
哦,可以肯定,但是没有硬件乘法器的小型Micro实际上很常见。而且,如果您在嵌入式土地上工作,并且想在每销售一百万个产品中节省0.10美元,那么您最好知道一些肮脏的把戏。节省下来的钱=额外的利润,这使您的老板很高兴。
quick_now 2011年

好吧,脏……只是乘以.0101010101(大约1/3)。专家提示:您还可以乘以.000100010001101(仅需3个.010101010101
移位

我怎么能只用整数而没有浮点数呢?
quick_now 2011年

1
按位,X * 101 = X + X <<类似地,X * 0.000100010001为x >> 4 + X >> 8 + X >> 12
MSalters

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.