首先我们知道,计算机以补码的形式存储的,正数的补码是原码不变,负数补码是通过原码计算得到,计算过程为:符号位不变,其余位按照原码取反加1。
对于8位有符号数(-128~ 0~127):
+127,原码0111 1111
对于-128我们来看,-128是个特殊的数,不用考虑符号位!若字长为1byte,因有一位是符号位,所以原码能表示数值的范围为(-127~ -0 ,+0~ 127)共256个。注:在补码中用(-128)代替了(-0),所以补码的表示范围为:(-128~ 0~127)共256个.
注:-128原码和补码一样的, 为1000 0000
下面,我们来看一下-129的值,-128的原码为 1000 0000 ,减1 等于0111 1111 (原码计算就没有那些规矩了,直接计算就行了),然后存储,计算机一看正数,就直接存储了+127.
左移运算符m<<n表示把m左移n位。往左移n位的时候,最左边的n位将被丢弃,同时在最右边补上n个0.
如:
00001010<<2=00101000
10001010<<3=01010000
右移运算符m>>n表示把m右移n位。往右移的时候,稍微有些复杂。
对于无符号数,丢弃最右边的n位,并用0填补最左边的n位。
对于有符号数:先转化为补码,丢弃最右边的n位,并用该数字的符号位填补最左边的n位。
下面是对两个8位有符号数(均为补码形式下)进行右移的例子:
00001010>>2=00000010
10001010>>3=11110001
接下来看题
思路1(有bug):
对输入的number和1按位与,这样就可以判断number转化为二进制后的最右位是否为1,如果为1,count++,然后把number右移一位,继续循环,直到number为0为止。
但这里存在致命的问题在于,如果输入的number是负数,比如一个负数的补码为0x80000000(32位),右移一位后最左边的一位会被1补上,即变成0xC0000000,如果一直这么右移下去,那么最终这个数字就会变成0xFFFFFFFF而陷入死循环。
改进的思路2(也是大部分人能想到的常规思路):
为了避免死循环,不对输入的number进行右移。首先把number和1(记为flag)进行按位与,判断number最右位(最低位)是不是1,然后把flag左移一位得到2,再和number按位与,判断number次低位是不是1。这样反复即可,最终flag为0时结束循环。总共的循环次数就是整数二进制的位数,32位的整数就要循环32次。
代码如下:
class Solution {
public:
int NumberOf1(int number)
{
int count = 0;
int flag = 1;
while (flag)
{
if (number&flag)
count++;
flag = flag << 1;
}
return count;
}
};
思路3(感觉如果实际面试,很难想到):
把number和number-1按位与,如果结果不为0,说明number中在最低位上的1被去除了。
如:
number为10110100
number-1为10110011
number&(number-1)为10110000,和number对比后发现number中在最低位上的1被去除了。
这样循环的次数减少了,number的二进制数中有几个1,就循环几次。
代码如下:
class Solution {
public:
int NumberOf1(int number)
{
int count = 0;
while (number)
{
count++;
number = number&(number - 1);
}
return count;
}
};