最近,我遇到了这个问题,要求您返回二进制字符串中连续数字的数量,从左到右(所以11101001 = 3
,011111 = 0
,{{1等等)。问题是只使用二进制操作数,而没有任何循环。
在网上浏览了一下之后(感谢Google),我找到了其他人提出的解决方案,并希望更好地帮助理解它。
111101 = 4
从我所知道的,该函数显然检查了32位整数的前16位,然后根据它们是否都是1来进入下一位,然后接下来的4位取决于它们是否全是1,然后是2,等1,等等。
不幸的是,我有点迷失代码是如何完成这一点的,并希望得到一些帮助理解。
例如,在这里:
int leftBitCount(int x) {
int v = x;
int r; // store our result here
int shift;
int full = !(~x); // we must add one if we have 0xffffffff
// Check the top 16 bits and add them to our result if they exist
r = !(~(v>>16)) << 4;
v <<= r;
// check the remaining 8 bits
shift = !(~(v >> 24)) << 3;
v <<= shift;
r |= shift;
// remaining 4 bits
shift = !(~(v>>28)) << 2;
v <<= shift;
r |= shift;
// remaining 2 bits
shift = !(~(v >> 30)) << 1;
v <<= shift;
r |= shift;
// remaining 1 bits
r ^= 1&((v>>31));
// remember to add one if we have 32 on bits
return r + full;
}
我可以看到r = !(~(v>>16)) << 4;
v <<= r;
正在被转移和否定,但我不知道这有助于解决任何事情。
答案 0 :(得分:2)
r = !(~(v>>16)) << 4;
以下是此行发生的事情:
v >> 16
将v
(此时此值为x
)的值移至右侧16个位置。这个表达式的低16位是v
的前16位,有趣的是,这个表达式的前16位是实现定义的。我曾经使用过的C编译器已将签名值的右移作为算术移位,而您发布的代码似乎依赖于这一事实,但并不一定如此。有关详细信息,请参阅this question的答案。
~
运算符执行步骤1中生成的值的按位补码。此处的重要观察是,当且仅当(a)v
的前16位全部1s,和(b)正在使用的C编译器执行算术移位,然后 all 步骤1中表达式的位将为1s,这意味着~(v>>16)
将为零。< / p>
!
运算符对步骤2中生成的值执行逻辑否定,这意味着当且仅当步骤2中的值为零时,!(~(v>>16))
将为1(表示v
的前16位都是1。否则,该值将为零。
最后,<< 4
只是乘以16.如果r
的前16位设置为1,则为x
赋值16,或者如果前16位中的任何一位为0(或者如果正在使用的C编译器执行逻辑右移),则值为零。
接下来,这一行:
v <<= r;
如果v
的前16位全部为1 - 意味着r
的值为16 - 那么我们就不需要再检查这些位了,所以这一行换了{16}左边的v
,它基本上丢弃了我们已经检查过的前16位,并将8个次最重要的位置于最高位置,因此我们可以检查下一个位。
另一方面,如果v
的前16位不所有1s - 意味着r
的值为0 - 那么底部 v
的16位变得无关紧要,因为无论v
中前导1位的数量是多少,我们都知道它必须小于16.所以在这种情况下,{{1保持v <<= r
的值不变,我们通过检查v
中的八个原始最顶部位继续下一步。
算法的其余部分基本上重复这些步骤,除了每个步骤中被检查的位数减半。所以这段代码检查前8位:
v
这里唯一不同的是shift = !(~(v >> 24)) << 3;
v <<= shift;
r |= shift;
正在使用逻辑OR进行扩充,而不是像在16位步骤中那样直接分配。在这种情况下,这在功能上等同于加法,因为我们知道r
的值先前为0或16,而r
的值同样为0或8,并且这些值都不是&# 39; 1位出现在相应的位置。然后这位代码与前4位相同:
shift
然后是前2:
shift = !(~(v>>28)) << 2;
v <<= shift;
r |= shift;
最后,前1:
shift = !(~(v >> 30)) << 1;
v <<= shift;
r |= shift;
这样测试的比特数是16 + 8 + 4 + 2 + 1 = 31,这显然是原始值中总比特数的一个,因此在所有32比特都是的情况下是不够的。 1秒。假设所有32位都是1,那么r ^= 1&((v>>31));
的原始值将在每一步中移位:
x
到目前为止看到的代码从未考虑过的是W位,即原始值的最低有效位。当且仅当左侧的所有位都是1时,该位将成为结果的一部分,这意味着该值中的所有位都是1,这是在此处执行检查的目的。代码顶部:
16-bit step: 123456789ABCDEFGHIJKLMNOPQRSTUVW
8-bit step: HIJKLMNOPQRSTUVW----------------
4-bit step: PQRSTUVW------------------------
2-bit step: TUVW----------------------------
1-bit step: VW------------------------------
这与我们之前看到的按位补码和逻辑否定的组合相同;当且仅当 int full = !(~x);
中的所有位都为1时,~x
将为零,因此x
在这种情况下为1,在任何其他情况下为零。
答案 1 :(得分:1)
好,
int full = !(~x); // we must add one if we have 0xffffffff
按位求反运算符(~
)翻转其操作数中的所有凹坑。如果设置了操作数中的所有位,则结果为零,逻辑否定运算符(!
)将转换为值1
(true)。否则,逻辑否定将其操作数转换为0
(false)。
// Check the top 16 bits and add them to our result if they exist
r = !(~(v>>16)) << 4;
假设为32位宽的操作数v
被右移,因此其上半部分的结尾位于结果的最后16位。看起来代码假设(不安全地)如果符号位被置位(即算术移位),空出的位将是一次填充,因此如果原始数字的前16位全部被设置,那么按位否定产生零,逻辑否定转换为1.左移1(== 2 ^ 0)四位产生16(== 2 ^ 4),这是所讨论的位数。如果不是所有前16位都是1(或者某些但不是所有这些位都被设置,并且右移位以零而不是1),那么最终会得到0 << 4
,这当然是0
v <<= r;
我们刚刚计算的位数(16或0)向左移位。我们现在考虑原始顶部位的较小块或其他位的较小块,现在转移到顶部位置。
shift = !(~(v >> 24)) << 3;
v <<= shift;
与以前相同的游戏,但现在我们只考虑一个8位的块。
r |= shift;
将刚测量的位数(8或0)添加到结果中。
其余的继续以相同的模式,到这里:
r ^= 1&((v>>31));
如果v
的顶部位置位,它只会翻转结果的最后一位;从那时起,代码未设置该位,^=
可以很容易地+=
或|=
。然后我们有:
return r + full;
请记住,如果参数设置了所有位,则full
取值为1,否则为零。上面的代码已经用尽了它一次设置r
的一个位所能发挥的所有技巧,因为它刚刚完成了最低有效位。如果输入值中确实还有一个位设置,则需要使用+
运算符将结果加1或模拟6位加法器。
请注意:此代码似乎依赖于某些实现定义的行为来完成其工作。您可以通过使用无符号整数而不是有符号整数来避免实现定义。你仍然需要稍微修改一下表达式,但我把它留给你来解决。