我偶然发现了一个询问是否you ever had to use bit shifting in real projects的问题。我在很多项目中都使用过相当多的位移,但是,我从来没有使用算术位移,即位移,其中左操作数可以为负和符号应该移位而不是零。例如,在Java中,您将使用>>
运算符进行算术位移(而>>>
将执行逻辑移位)。在经过深思熟虑后,我得出的结论是,我从未使用过>>
左侧操作数可能为负数。
如this answer中所述,算术移位甚至是在C ++中定义的实现,因此与Java相比 - 在C ++中甚至没有用于执行算术移位的标准化运算符。答案还说明了一个有趣的问题,即我甚至都不知道的负面数字:
+63 >> 1 = +31 (integral part of quotient E1/2E2)
00111111 >> 1 = 00011111
-63 >> 1 = -32
11000001 >> 1 = 11100000
所以-63>>1
会产生-32
,这在查看这些位时很明显,但也许不是大多数程序员在第一眼就能看到的。更令人惊讶的是(但在查看位时再次显而易见)是-1>>1
是-1
,而不是0
。
那么,可能负值算术右移的具体用例是什么?
答案 0 :(得分:6)
最着名的可能是无分支绝对值:
int m = x >> 31;
int abs = x + m ^ m;
使用算术移位将signbit复制到所有位。我遇到的大多数算术移位用途都是那种形式。当然,算术转移不是必需的,你可以用x >> 31
替换所有出现的x
(其中int
是-(x >>> 31)
)
值31来自int
的大小,以Java为单位,根据定义为32。因此,向右移位31会移除除符号位之外的所有位,因为它(因为它是算术移位)被复制到那些31位,在每个位置都留下了符号位的副本。
答案 1 :(得分:1)
之前,它已经派上用场了,用于制作面具然后用于'&'或'|'操作位字段时的运算符,用于按位数据打包或按位图形。
我没有方便的代码示例,但我记得多年前在黑白图形中使用该技术进行放大(通过扩展一点,1或0)。对于3倍变焦,'0'将变为'000'而'1'将变为'111'而不必知道该位的初始值。要扩展的位将被置于高位置,然后算术右移将扩展它,无论它是0还是1.逻辑移位(左或右)总是引入零来填充空位位置。在这种情况下,符号位是解决方案的关键。
答案 2 :(得分:0)
我不太清楚你的意思。我打算推测你想使用位移作为算术函数。 我看到的一个有趣的事情是二进制数的这个属性。
int n = 4;
int k = 1;
n = n << k; // is the same as n = n * 2^k
//now n = (4 * 2) i.e. 8
n = n >> k; // is the same as n = n / 2^k
//now n = (8 / 2) i.e. 4
希望有所帮助。
但是,是的,你要小心负数 我会掩盖,然后相应地将其转回来
答案 3 :(得分:0)
这是一个函数的例子,它将找到大于或等于输入的2的最小幂。这个问题的其他解决方案可能更快,任何面向硬件的解决方案或只是一系列右移和OR。该解决方案使用算术移位来执行二进制搜索。
unsigned ClosestPowerOfTwo(unsigned num) {
int mask = 0xFFFF0000;
mask = (num & mask) ? (mask << 8) : (mask >> 8);
mask = (num & mask) ? (mask << 4) : (mask >> 4);
mask = (num & mask) ? (mask << 2) : (mask >> 2);
mask = (num & mask) ? (mask << 1) : (mask >> 1);
mask = (num & mask) ? mask : (mask >> 1);
return (num & mask) ? -mask : -(mask << 1);
}
答案 4 :(得分:0)
实际上逻辑右移更为常用。但是,有许多操作需要算术移位(或者用算术移位可以更优雅地解决)
签名扩展名:
value >> 6
将把这些位移到低10位,并扩展符号以保留该值。如果将它们读入低10位,而高6位为零,则将使用value << 6 >> 6
来对值进行扩展以对其进行处理struct bitfield {
int x: 15;
int y: 12;
int z: 5;
};
int f(bitfield b) {
return (b.x/8 + b.y/5) * b.z;
}
Demo on Godbolt。移位是由编译器生成的,但是通常您不使用位域(因为它们不可移植),而是对原始整数值进行操作,因此您需要自己进行算术移位以提取字段char* pointer = (char*)((intptr_t)address << 16 >> 16)
。您可以将其视为底部的48位位域 Round signed division properly,当converting to a multiplication(例如x/12
)将被优化为x*43691 >> 19
时,需要进行一些附加舍入。当然,您永远都不会在普通的标量代码中执行此操作,因为编译器已经为您完成了此操作,但是有时您可能需要对代码进行矢量化或make some related libraries,然后您需要使用算术移位来计算舍入。您可以在上面的位域的输出程序集中看到编译器如何舍入除法结果
饱和移位或大于位宽的移位,即当移位计数> =位宽时该值变为零
uint32_t lsh_saturated(uint32_t x, int32_t n) // returns 0 if n == 32
{
return (x << (n & 0x1F)) & ((n-32) >> 5);
}
uint32_t lsh(uint32_t x, int32_t n) // returns 0 if n >= 32
{
return (x << (n & 0x1F)) & ((n-32) >> 31);
}
位掩码,在无分支选择(例如muxer)等各种情况下很有用。您可以看到许多在有名的bithacks page上有条件地做某事的方法。其中大多数是通过生成全1或全0的掩码来完成的。掩码通常是通过传播减法的符号位来计算的,例如(x - y) >> 31
(对于32位整数)。当然,可以将其更改为-(unsigned(x - y) >> 31)
,但这需要2的补码,并且需要更多的操作。这是无需分支即可获取两个整数的最小值和最大值的方法:
min = y + ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1)));
max = x - ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1)));
另一个示例是Compute modulus division by (1 << s) - 1 in parallel without a division operator中的m = m & -((signed)(m - d) >> s);
答案 5 :(得分:-1)
在C中编写器件驱动程序时,由于位被用作需要打开和关闭的开关,因此广泛使用位移操作符。位移允许人们轻松正确地定位正确的开关。
许多散列和加密函数都使用了位移。看看Mercenne Twister。
最后,使用位域来包含状态信息有时很有用。包括位移的位操作函数对于这些事情很有用。