今天向各位介绍一个在C语言有关的二进制神奇的式子:n=n&(n-1)
一,内涵,作用
首先我们来解读它的涵义:n与n-1按位与之后赋值给n,这能产生产生什么效果呢,
举个例子:
1,n=-1
n在内存中以补码的方式存储,其补码为11111111111111111111111111111111(32个1),n-1即为11111111111111111111111111111110;两者按位与&,同位上有0为0,同为1为1,得到的结果为11111111111111111111111111111110,同时n被赋值,如此循环往复……
n值(补码)的变化:
11111111111111111111111111111111 ==>
11111111111111111111111111111110 ==>
11111111111111111111111111111100 ==>
11111111111111111111111111111000 ==>
11111111111111111111111111110000 ==>
11111111111111111111111111100000 ==>
…………
00000000000000000000000000000000
2,n=10100000010001001010001011001010(这里直接写个补码)
n-1即为10100000010001001010001011001001,两者按位与&,同位上有0为0,同为1为1,得到的结果为10100000010001001010001011001000,同时n被赋值,如此循环往复……
n值(补码)的变化:
10100000010001001010001011001001 ==>
10100000010001001010001011001000 ==>
10100000010001001010001011000000 ==>
10100000010001001010001010000000 ==>
10100000010001001010001000000000 ==>
10100000010001001010000000000000 ==>
…………
00000000000000000000000000000000
小结:
从上面的两个例子我们可以发现n值(补码)的变化是有规律的,就是每次变化后它最右边的1会变成0,究其原因,是因为,n-1相当与将n最右边的1及其右边的所有位(都是0)按位取反,两者按位与&,这一部分都化为0,从结果看就是n最右边的1化为了0
二,应用场景
那这个神奇的式子有什么用呢?
1,统计二进制中1的个数
主体框架:
int main()
{
int n = 0;
while (~scanf("%d", &n))
{
int ret = Count(n);
printf("%d\n", ret);
}
return 0;
}
用两种方式实现Count函数,以此初窥n=n&(n-1)的神奇之处
(1)n右移与1按位与,循环32次,统计n补码中1的个数
int Count(int n)
{
int i = 0;
int ret = 0;
for (i = 0; i < 32; i++)
{
if (((n >> i) & 1) == 1)
{
ret++;;
}
}
return ret;
}
(2)使用神奇的式子,n每次最右边的1都变为0,有几个1就循环几次
int Count(int n)
{
int i = 0;
while (n)
{
n = n & (n - 1);
i++;
}
return i;
}
2,判断一个数是不是2的k次方
主体框架:
int main()
{
int n = 0;
scanf("%d", &n);
Judge(n); //是打印YES,否打印NO
return 0;
}
Judge函数实现:
void Judge(int n)
{
if ((n = (n & (n - 1))) == 0)
{
printf("YES\n");
}
else
{
printf("NO\n");
}
}
注:
如果一个数是2的k次方,则这个数的二进制中只有一个位上为1