题目描述:
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
一道好题!!
做完这道题,加深了我对计算机存储数字的形式以及位运算的理解!!
假设一个int为8字节
在计算机中,一个数字以二进制的形式存储,对于一个int类型的数字,最高位为符号位,0为正,1为负。他们全都是以补码的形式存储的。
对于一个正整数,它的原码反码补码都是它的二进制表示,而对于一个负数,它的源码是其相反数的二进制表示,然后最高位变为1,比如-1,按照上述说明,它的原码就是10000001,反码就是除了符号位,其余位置按位取反,即11111110,补码就是在反码的基础上+1,即11111111。
在这里有一个地方需要注意,那就是如果一个数的反码为11111111,那么它的补码怎么表示?其实反码11111111的原码是10000000,即-0,我们知道在0的前面加符号是没有意义的,因此,我们规定10000000表示-128,即负数多了一个表示,这也就是为什么我们通常所说的32位int的取值范围是[-2147483648 ~ 2147483647],而由于整数在计算机内是以补码的形式保存的,因此,-128的补码就规定为-10000000,它没有原码和反码,这样做也是的0有了唯一的表示。
我们回到这个题,他让我们求一个整数的二进制有多少个1,根据上述所说的,我们知道了整数以补码形式保存,因此,我们就不用纠结如何现将负数的补码求出来,再判断有几个1了,直接撸就行。。。
怎么撸又是一个问题,如果我们按照通常的做法,与1进行与运算,再右移这个数,但是注意负数可不能右移运算。在说原因之前,我们先明确两个概念:
逻辑右移:二进制右移后,用0补位。
算数右移:右移后,符号不变。
逻辑右移比较好理解,这里重点说一下算数右移:
比如-128,它的补码为10000000,算数右移就是首先右移一位,得到01000000,然后首位补1,得到最终的11000000,它的反码是10111111,原码是11000000,即-64。
明白了什么是算术右移和逻辑右移,我们也就明白了为什么负数不能右移运算,这是因为一般来说计算机右移操作都是算数右移,因此负数最高位1会被永久保留,这样得出的值必然是不正确的。
但是左移操作一定是逻辑左移,这样,对于任意一个数,先让其跟1与运算,然后将1左移,当1左移超出int位数限制的时候,我们就可以认为它检查完所有的位数了,这样就能得到正确的结果。
#include <cstdio>
#include <iostream>
using namespace std;
int main()
{
int T;
int n, ans;
int f;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
ans = 0;
f = 1;
while(f){
if(f & n)
ans++;
f = f << 1;
}
printf("%d\n", ans);
}
return 0;
}
代码虽短,但是蕴含了很多知识,希望大家能够掌握,不足之处望指正。