二进制中原码、反码、补码以及如何计算补码,附力扣算法题

二进制

二进制和十进制一样,也是一种进位计数制,但是它的基数是 2。二进制表达式中 0 和 1 的位置不同,它所代表的数值也不同。例如,二进制数 0000 1010 表示十进制数 10。一个二进制数具有两个基本特点:两个不同的数字符号,即 0 和 1,逢二进一。

  • 十进制与二进制数之间的转换
    用计算机处理十进制数时,必须先把它转化为二进制数才能被计算机所接受;同理,计算结果应该将二进制数转换成人们习惯的十进制数。

  • 十进制转换成二进制
    把一个十进制转换为二进制的方法是:把被转换的十进制数反复地除以 2,直到商为 0 为止,所得余数(从末位读起)就是这个数的二进制表示,简单地说,就是 “除 2 取余法”。
    二进制中原码、反码、补码以及如何计算补码,附力扣算法题_第1张图片

  • 二进制转十进制
    要把二进制转换为十进制数,只要将二进制数按权展开求和即可。
    二进制中原码、反码、补码以及如何计算补码,附力扣算法题_第2张图片

二进制中码的概念

1、原码:

一个正数,按照绝对值大小转换成的二进制数;一个负数按照绝对值大小转换成的二进制数,然后最高位补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个数的绝对值大小,然后进行加减操作,最后运算结果的符号还要与大的符号相同;于是,反码产生了。

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,没过多久,反码就成为了过滤产物,也就是,后来补码出现了。

3、补码

正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加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。

Java中如何计算

  • 如果计算反码?
  • 如何计算补码?
  • 如何输出二进制字符串?
  • 不使用"除2取余法"如何计算二进制串?

下面代码是上述问题的解答(简单写了个程序,肯定不是最优解,但适合讲解,细节可以自行优化)

二进制中1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘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 运算符

你可能感兴趣的:(算法,leetcode,java,二进制)