二进制和十进制一样,也是一种进位计数制,但是它的基数是 2。二进制表达式中 0 和 1 的位置不同,它所代表的数值也不同。例如,二进制数 0000 1010 表示十进制数 10。一个二进制数具有两个基本特点:两个不同的数字符号,即 0 和 1,逢二进一。
十进制与二进制数之间的转换
用计算机处理十进制数时,必须先把它转化为二进制数才能被计算机所接受;同理,计算结果应该将二进制数转换成人们习惯的十进制数。
十进制转换成二进制
把一个十进制转换为二进制的方法是:把被转换的十进制数反复地除以 2,直到商为 0 为止,所得余数(从末位读起)就是这个数的二进制表示,简单地说,就是 “除 2 取余法”。
一个正数,按照绝对值大小转换成的二进制数;一个负数按照绝对值大小转换成的二进制数,然后最高位补1,称为原码。
比如 00000000 00000000 00000000 00000101 是 5的 原码。
10000000 00000000 00000000 00000101 是 -5的 原码。
备注:
比如byte类型,用2^8来表示无符号整数的话,是0 - 255了;如果有符号, 最高位表示符号,0为正,1为负,那么,正常的理解就是 -127 至 +127 了.这就是原码了,值得一提的是,原码的弱点,有2个0,即+0和-0(10000000和00000000);还有就是,进行异号相加或同号相减时,比较笨蛋,先要判断2个数的绝对值大小,然后进行加减操作,最后运算结果的符号还要与大的符号相同;于是,反码产生了。
正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反[每一位取反(除符号位)]。
取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)
比如:正数00000000 00000000 00000000 00000101 的反码还是 00000000 00000000 00000000 00000101
负数10000000 00000000 00000000 00000101
的反码则是 11111111 11111111 11111111 11111010
。
反码是相互的,所以也可称:10000000 00000000 00000000 00000101 和 11111111 11111111 11111111 11111010互为反码。
备注:还是有+0和-0,没过多久,反码就成为了过滤产物,也就是,后来补码出现了。
正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1.
比如:10000000 00000000 00000000 00000101 的补码是:11111111 11111111 11111111 11111010。
那么,补码为:
11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
备注:1、从补码求原码的方法跟原码求补码是一样的 ,也可以通过完全逆运算来做,先减一,再取反。
2、补码却规定0没有正负之分
所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。
下面代码是上述问题的解答(简单写了个程序,肯定不是最优解,但适合讲解,细节可以自行优化)
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用 二进制补码 记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
示例 1:
输入:n = 11 (控制台输入 00000000000000000000000000001011)
输出:3
解释:输入的二进制串
00000000000000000000000000001011 中,共有三位为 ‘1’。
示例 2:
输入:n = 128 (控制台输入 00000000000000000000000010000000)
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。
示例 3:
输入:n = 4294967293 (控制台输入 11111111111111111111111111111101,部分语言中 n = -3)
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 ‘1’。
提示:
输入必须是长度为 32 的 二进制串 。
-2^32
< n < 2^32 - 1
也就是Integer.MIN_VALUE到Integer.MAX_VALUE的范围。二进制的表示中最高位表示符号,0为整,1为负。
public class hammingWeight {
/**
*
* @param n
* @return
*/
public static int hammingWeight(long n) {
if (Integer.MIN_VALUE == n) {
return 1;
}
// 这里int是-2^32 < n < 2^32 -1 所以需要数组有32个元素来承接0或1,boolean true 表示1, false 表示 0,其中数组的最后一位元素用来保存二进制符号
boolean[] _1 = new boolean[32];
// 这里定义缓存是为了防止重复计算,只有在第一次的时候进行次方运算
Map<Integer, Long> cache = new HashMap<>();
// 为什么取绝对值?因为负数要转换为正整数进行计算补码
long sum = Math.abs(n);
// 判断是否为负数
boolean isPlus = n >= 0;
// 统计1出现的次数
int total = 0;
while (sum != 0) {
int count = 0;
long pow = 0;
while (sum > pow) {
Long aLong = cache.get(count);
if (aLong == null) {
pow = (long) Math.pow(2, count);
cache.put(count, pow);
System.out.println(count);
} else {
pow = aLong;
}
if (pow > sum) {
sum -= pow / 2;
_1[count - 1] = true;
break;
} else if (pow == sum) {
sum -= pow;
_1[count] = true;
break;
} else {
count ++;
}
}
}
if (!isPlus) { // 处理补码
// 正整数
printBinary("正整数", _1);
// 取反
for (int i = 0; i < _1.length; i++) {
if (_1[i]) {
_1[i] = false;
} else {
_1[i] = true;
}
}
printBinary("取反", _1);
// + 1
if (_1[0]) {
for (int i = 0; i < _1.length - 1; i++) {
if (_1[i]) {
_1[i] = false;
} else {
_1[i] = true;
break;
}
}
} else {
_1[0] = true;
}
_1[_1.length - 1] = true;
printBinary("+1", _1);
}
for (boolean b : _1) if (b) total++;
return total;
}
public static void printBinary (String name, boolean[] arr) {
StringBuilder stringBuilder = new StringBuilder();
arr2List(arr).forEach(obj -> {
if (obj) stringBuilder.append("1");
else stringBuilder.append("0");
});
System.out.println(name + ":" + stringBuilder.toString());
}
public static List<Boolean> arr2List (boolean[] arr) {
List<Boolean> bo = new ArrayList<>();
for (int i = arr.length - 1; i >= 0; i--) {
bo.add(arr[i]);
}
return bo;
}
public static void main(String[] args) {
// 4294967293
// 2147483647
System.out.println((long) Math.pow(2, 31));
int i = hammingWeight(-2147483648);
System.out.println(i);
//if (4294967293L > Integer.MAX_VALUE) {
// System.out.println("ok");
// System.out.println((long)Math.pow(2, 16));
// System.out.println((long)Math.pow(2, 32));
//}
}
}
① 将10进制转二进制用一个boolean[]数组来承接,数组中元素 false表示0,true表示1。这样我们很容易遍历boolean数组就可以把二进制字符串表示出来。
②十进制计算二进制的核心算法就是判断n 是否大于2 ^ k,k表示次方。如果n == 2 ^ k则boolean数组b[k] = true,并且n = n - 2 ^ k次方。如果n < 2 ^ k 则b[k - 1] = true, 并且n = n - 2 ^ k / 2。如果n > 2 ^ k则k ++ 知道满足上述的两个条件。
③接着如果n为负数则求反码,根据反码再求补码
④遍历boolean数组统计true元素出现的次数就是本题答案。
⑤求反码很简单,只需要把true变成false,false变为true即可,求补码就需要考虑“进位”的问题,比如01 + 01 就需要进以为变成10,111 + 001 = 1000。1001 + 0001 = 1010。详细可以看上面代码如何实现。
① 二进制中最高位表示符号位
② 负数来说反码就是原码非符号位取反,0 变成1,1变成0。对于正数来说就是反码就是原码不需要计算。
③ 负数来说补码就是反码 + 1,正数来说补码就是原码
④ 10进制转2进制的方法有很多,上述介绍了只介绍了两种
参考文章
负数的二进制表示方法(正数:原码、负数:补码)
Java 运算符