我不熟悉按位操作。我有这个序列:
1 0 0 0 0 : 16
---------------
0 1 1 1 1 : 15
---------------
0 1 1 1 0 : 14
---------------
.
.
.
---------------
0 0 0 1 1 : 3
---------------
0 0 0 1 0 : 2
---------------
0 0 0 0 1 : 1
---------------
我想首先检查是否有多个“1”。如果是这种情况,我想删除具有较大小数值的那个,并完成,获得更大的剩余。例如15,有四个“1”,我删除较大的一个,“1”在“8”,我得到“0 0 1 1 1:7”,其中较大的“1”在“4”。我怎么能这样做?
答案 0 :(得分:1)
这是执行您想要的代码:
unsigned chk_bits(unsigned int x) {
unsigned i;
if (x != 0 && (x & (x-1)) != 0) {
/* More than one '1' bit */
for (i = ~(~0U >> 1); (x & i) == 0; i >>= 1)
; /* Intentionally left blank */
return x & ~i;
}
return x;
}
请注意,我假设您正在处理无符号数字。这通常更安全,因为右移是由有符号整数定义的实现,因为符号扩展。
if
语句检查x
中是否设置了多个位。 x & (x-1)
是一种已知的方法,可以使用第一个' 1'来获得与x
相同的数字。最低有效位关闭(例如,如果x
为101100100
,则x & (x-1)
为101100000
。因此,if
表示:
如果x不为零,并且关闭第一位设置为1(从LSB到MSB)会导致某些不为0, 然后...
这相当于说x
中设置的位数超过1位。
然后,我们遍历x
中的每一位,停在设置的第一个最高位。 i
被初始化为1000000000000000000000000000
,并且循环保持正确移位直到x & i
求值为非零值,此时我们发现第一个最重要的位是{{1} }}。此时,取1
补码将产生掩码以关闭i
中的此位,因为x
是一个数字,每个位设置为1,除了唯一位为~i
的位(对应于1
中的最高位)。因此,使用x
与此进行对比可以为您提供所需的内容。
代码是可移植的:它不承担任何特定的表示,也不依赖于x
是32或64位的事实。
更新:我在阅读您的评论后添加了更详细的说明。
第1步 - 了解unsigned
的作用:
我们必须考虑两种可能性:
x & (x-1)
以1(x
).......0011001
以0(x
)在第一种情况下,很容易看到.......0011000
只是x-1
,最右边的位设置为0.例如,x
,所以,有效地,{{1只会是0011001 - 1 = 0011000
。
在第二种情况下,可能稍微难以理解,但如果x & (x-1)
的最右边的位为0,则x-1
将为x
,每0位切换为1位,从最低有效位开始,直到找到1,然后变为0。
让我举个例子,因为对于刚来这个的人来说这可能很棘手:
x-1
为什么?因为以0结尾的二进制数的先前数字是在最右边位置填充有一个或多个1位的二进制数。当我们增加它时,如x
,我们必须增加下一个" free"位置,即下一个0位置,将其变为1,然后该位置右侧的所有1位都变为0.这是任何base-n计数的工作方式,唯一的区别是对于base-2,你只有0&1;和1' s。
考虑基数10计数的工作原理。当我们用完数字时,值会回绕,我们在左侧添加一个新数字。 1101011000 - 1 = 11101010111
之后会发生什么?好吧,计数再次重置,左边是一个新数字,9的回合为0,结果是10101111101111 + 1
。二进制算法也是如此。
考虑以二进制计数的过程;我们只有2位,0和1:
看看模式是如何完全按照我的描述进行的?
请记住,我们正在考虑第二种情况,其中999
以0结尾。在1000
与x
进行比较时,x-1
上最右边的0' x
中的1&,x
中最右边的1现在在x-1
中为0。因此,x
中唯一保持相同的部分是在1的左侧变为0。
因此,x-1
将与x
相同,直到第一个最右边的1位为止。现在我们可以看到,在这两种情况下,x & (x-1)
实际上会删除x
的最右边1位。
第二步:究竟是什么x & (x-1)
?
字母x
代表~0U >> 1
。在C中,整数常量的类型为U
,除非您指定它。将unsigned
附加到整数常量会使其无符号。我之所以使用它,是因为正如我前面提到的那样,实现定义了右移是否使符号扩展。一元运算符int
是补码运算符,它抓取一个数,并取其补码:每0位变为1,每1位变为0.因此,~0是一个填充1&#的数字39; s:U
。然后我将它向右移动一个位置,所以现在我们有:~
,并且这个表达式是11111111111...
。最后,我将其作为补充,以获取01111111....
,其代码为~0U >> 1
。这只是一种可移植的方法来获取一个数字,最左边的位设置为1,每隔一个设置为0。
您可以查看K& R第2章,更具体地说,第2.9节。从第48页开始,将显示按位运算符。练习2-9向读者提出挑战,解释为什么100000....
有效。如果您不知道,K& R是一本描述由C的创作者Kernighan和Ritchie编写的C编程语言的书。书名为#C; C编程语言",我推荐你得到第二版的副本。每个优秀的C程序员都从本书中学到了C语言。
答案 1 :(得分:0)
我想首先检查是否有多个“1”。
如果一个数字的二进制表示中只有一个1
,那么它就是一个可以用2 x 形式表示的数字。例如,
4 00000100 2^2 32 00010000 2^5
因此,要检查单个属性,您可以检查此属性。
如果log 2 ( x )是一个整数,那么它的二进制表示就是单1
。
你可以calculate登录 2 ( x )
log 2 ( x )= log y ( x )/ log y (2)
其中 y 可以是任何内容,标准日志功能为10或 e 。
这是一个解决方案
double logBase2 = log(num)/log(2);
if (logBase2 != (int)logBase2) {
int i = 7;
for (;i >0 ; i--) {
if (num & (1 << i)) {
num &= ~(1 << i);
break;
}
}
}
double logBase2 = log(num)/log(2);
if (logBase2 != (int)logBase2) {
int i = 7;
for (;i >0 ; i--) {
if (num & (1 << i)) {
num &= ~(1 << i);
break;
}
}
}