比特操作,获得更大的价值

时间:2013-11-04 05:10:32

标签: c++ bit-manipulation

我不熟悉按位操作。我有这个序列:

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”。我怎么能这样做?

2 个答案:

答案 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相同的数字。最低有效位关闭(例如,如果x101100100,则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:

  • 0(十进制0)
  • 1(十进制1 - 现在,我们用完了比特。对于下一个数字,这个1将变为0,我们需要在左边添加一个新位)
  • 10(小数2)
  • 11(十进制3 - 进程将再次重复 - 我们用完了比特,所以现在这两个比特将变成0并且必须添加左边的新位)
  • 100(十进制4)
  • 101(小数5)
  • 110(同样的过程再次重复)
  • 111
  • ...

看看模式是如何完全按照我的描述进行的?

请记住,我们正在考虑第二种情况,其中999以0结尾。在1000x进行比较时,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; } } }