输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示

如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

n&(n-1) : 将n的最后一个1置为0.
    public int NumberOf1(int n) {
        int count =0;
        while(n!=0){
            count++;
            n=n&(n-1);
        }
        return count;
    }

注意 底层计算全部都是补码!! 包括-1
输入 4 输出 1
输入 -4 输出 30 (int 是32位的, -4的补码是 1,1111111...100, 32-2=30位。)

牛逼Plus 128-1=127

128: 1,000 0000 ; -1: 1,111 1111
128-1: 0, 111 1111 【太牛逼了! 欧亨利式妙蛙】

原码,反码,补码

原码:二进制表示,第一位是符号位。0正,1负。
反码:负数 原码除符号位外取反,正数同原码。
补码:负数 反码+1,正数同原码。

正数都一样,主要记负数:反码+1
内核没有减法,-1 是 +(-1)

位运算

在位运算中,符号位也参与运算。

位与(and):& (有0则0)
位或(or):|(有1则1)
位异或:^(相同为0,不同为1)
取反:~ (按位取反)

<< 将最高位舍去,末尾补0;
>> 最高位符号位不变。其余右移。
>>> 无符号右移,左边直接补0.

补码加法中的自然丢弃和溢出。

十进制数相加:(-15) + (-20) = (-35)
用补码计算,就会有自然丢弃的现象。

(-15) 补= 1111 0001
(-20) 补= 1110 1100
相加:--------
可得: (1)1101 1101

括号中的1,是进位,自然丢弃。

剩下的 1101 1101,就是(-35)补。


八位的补码,可以表示十进制数-128~+127。
运算结果超出这个范围,就溢出了。

十进制数计算:(+80) + (+90) = (+170)
用补码计算,就会有溢出。

(+80) 补= 0101 0000
(+90) 补= 0101 1010
相加:--------
可得: (0) 1010 1010

进位是0。
剩下的 1010 1010,却是(-86)补。

为什么不是 (+170)补?
就是因为,超出范围,溢出了。