java低级运算-位运算

Java中的位运算

原码:用第一位表示符号,其余位表示值。

反码:正数的补码反码是其本身,负数的反码是符号位保持不变,其余位取反。

补码:正数的补码是其本身,负数的补码是在其反码的基础上+1。


正数: 原码=反码=补码

负数:反码=原码除符号位每位取反; 补码 = 反码+1


1、基本位运算

    private static void println(String msg, int r){
        System.out.println(msg + " ====> "+r);
    }
    public static void main(String[] args) {
        //1、左移<<
        println("5<<2", 5<<2);
        /**
         * 5<<2
         * 原码:0000 0000 0000 0000 0000 0000 0000 0101
         * 反码:0000 0000 0000 0000 0000 0000 0000 0101
         * 补码:0000 0000 0000 0000 0000 0000 0000 0101
         * 左移:0000 0000 0000 0000 0000 0000 0001 0100 (即补码)
         * 补码:0000 0000 0000 0000 0000 0000 0001 0100
         * 反码:0000 0000 0000 0000 0000 0000 0001 0100
         * 原码:0000 0000 0000 0000 0000 0000 0001 0100
         * 打印-结果:20
         * 计算规则:1、向左移动指定位,左边二进制位丢弃,右边补0。(注意byte和short类型移位运算时会变成int型,结果要强制转换)
         *           2、若1被移位到最左侧,则变成负数。
         *           3、左移时舍弃位不包含1,则左移一次,相当于乘2。
         **/
        println("-5<<1",-5<<1);
        /**
         * -5<<1
         * 原码:1000 0000 0000 0000 0000 0000 0000 0101
         * 反码:1111 1111 1111 1111 1111 1111 1111 1010
         * 补码:1111 1111 1111 1111 1111 1111 1111 1011
         * 移位:1111 1111 1111 1111 1111 1111 1111 0110 (即补码)
         * 补码:1111 1111 1111 1111 1111 1111 1111 0110
         * 反码:1111 1111 1111 1111 1111 1111 1111 0101
         * 原码:1000 0000 0000 0000 0000 0000 0000 1010
         * 打印-结果:-10
         *
         **/
        println("-5<<29",-5<<29); //PS:这样把0移到符号位,是不是就变成正数了?
        /**
         * -5<<29
         * 原码:1000 0000 0000 0000 0000 0000 0000 0101
         * 反码:1111 1111 1111 1111 1111 1111 1111 1010
         * 补码:1111 1111 1111 1111 1111 1111 1111 1011
         * 移位:0110 0000 0000 0000 0000 0000 0000 0000 (即补码)
         * 补码:0110 0000 0000 0000 0000 0000 0000 0000
         * 反码:0110 0000 0000 0000 0000 0000 0000 0000
         * 原码:0110 0000 0000 0000 0000 0000 0000 0000
         * 打印-结果:1610612736 正数!
         * 2^30+2^29 = 1610612736
         * 所以,同理,如果把正数的1 移到符号位,正数也变成了负数
         **/
        
        //------------------------------------分割线------------------------------------
        //2、右移
        println("5>>2", 5>>2);
        /**
         * 5>>2
         * 原码:0000 0000 0000 0000 0000 0000 0000 0101
         * 补码:0000 0000 0000 0000 0000 0000 0000 0101
         * 反码:0000 0000 0000 0000 0000 0000 0000 0101
         * 右移:0000 0000 0000 0000 0000 0000 0000 0001 (即补码)
         * 补码:0000 0000 0000 0000 0000 0000 0000 0001
         * 反码:0000 0000 0000 0000 0000 0000 0000 0001
         * 原码:0000 0000 0000 0000 0000 0000 0000 0001
         * 打印-结果:1
         * 计算规则:1、数值value向右移动num位,正数左补0,负数左补1,右边舍弃。(即保留符号位)
         *         2、右移一次,相当于除以2,并舍弃余数。
         **/
        
        //------------------------------------分割线------------------------------------
        //3、无符号右移
        println("-5>>>3",-5>>>3);
        /**
         * -5>>>3
         * 原码:1000 0000 0000 0000 0000 0000 0000 0101
         * 反码:1111 1111 1111 1111 1111 1111 1111 1010
         * 补码:1111 1111 1111 1111 1111 1111 1111 1011
         * 右移:0001 1111 1111 1111 1111 1111 1111 1111 (即补码)
         * 补码:0001 1111 1111 1111 1111 1111 1111 1111
         * 反码:0001 1111 1111 1111 1111 1111 1111 1111
         * 原码:0001 1111 1111 1111 1111 1111 1111 1111
         * 打印-结果:536870911
         * 计算规则:1、数值value向右移动num位,正数左补0,负数左补1,右边舍弃。(即保留符号位)
         *         2、右移一次,相当于除以2,并舍弃余数。
         *         3、无符号右移>>>:左边位用0补充,右边丢弃。
         *         所得结果永远正数
         **/

        //------------------------------------分割线------------------------------------
        //4、位与
        println("5 & 3",5 & 3);
        /**
         * 5 & 3
         * 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
         * 3 补码:0000 0000 0000 0000 0000 0000 0000 0011
         * 比较值:0000 0000 0000 0000 0000 0000 0000 0001
         * 打印-结果: 1
         * 计算规则:第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0
         **/

        //------------------------------------分割线------------------------------------
        //5、位或
        println("5 | 3",5 | 3);
        /**
         * 5 | 3
         * 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
         * 3 补码:0000 0000 0000 0000 0000 0000 0000 0011
         *   结果:0000 0000 0000 0000 0000 0000 0000 0111
         * 打印-结果:7
         * 计算规则:第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0
         **/

        //------------------------------------分割线------------------------------------
        //6、位异或
        println("5 ^ 3",5 ^ 3);
        /**
         * 5 | 3
         * 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
         * 3 补码:0000 0000 0000 0000 0000 0000 0000 0011
         *   结果:0000 0000 0000 0000 0000 0000 0000 0110
         * 打印-结果:6
         * 计算规则:第一个操作数的的第n位于第二个操作数的第n位 相反,那么结果的第n为也为1,否则为0
         **/

        //------------------------------------分割线------------------------------------
        //7、位非
        println("~5",~5);
        /**
         * 5 补码:0000 0000 0000 0000 0000 0000 0000 0101
         *   取非:1111 1111 1111 1111 1111 1111 1111 1010 (即补码)
         *   补码:1111 1111 1111 1111 1111 1111 1111 1010
         *   反码:1111 1111 1111 1111 1111 1111 1111 1001
         *   原码:1000 0000 0000 0000 0000 0000 0000 0110
         *   打印-结果:-6
         *   计算规则:操作数的第n位为1,那么结果的第n位为0,反之。
         **/
        //------------------------------------分割线------------------------------------
    }

 

2、取反运算~

  2.1 计算1 : ~32 结果: -33

        正数:32 二进制 原码为 0010 0000 

       1、计算机在保存数据时存的是补码  0010 0000  (正数的补码就是原码本身,负数的补码是原码按位取反再加1)

            所以在进行位非运算时,直接进行计算

            ~32 按位取反 位非运算: 1101 1111

       2、这个计算结果是计算后的补码形式,即计算机以补码的形式存储的内容,

            由于负数的补码是按原码按位取反再加一

            所以,要获得计算后的原码,我们需要先的到反码(及 补码减1)

            ~32 的反码为: 1101 1110

            再求原码:~32 原码: 1010 0001  即 -33

            所以可以知道,~32在计算机存储是按补码形式,~32是-33在计算机中的补码

 

  2.2 计算2: ~(-32)结果为 31

         负数:-32

       1、     原码: 1010 0000

           -32反码:1101 1111   (符号位不变,原码按位取反)

           -32补码:1110 0000   (反码+1)

           位非运算:  0001 1111

           反码: 0001 1111

           原码:0001 1111  (31) 

整个计算过程可以总结为: 数字 x ->原码->反码->补码->(计算步骤)进行取反操作(获得~x在计算机存贮的值) 获得取反后补码->反码->原码->十进制

 

   2.3 计算3: ~(-1L<<12L) 结果为 4095

 

3、PS:一个字节能存储的最大数是255

      字节是存储器的基本单位,长度为8位,计算机存储器中数据都是以二进制保存的,每一位不是0就是1,所以8个二进制位全为1时,代表的数最大。二进制的11111111,转换为十进制就是255。
存储单元存储的数据和存储单元的数据类型有关,一般用一个字节存储的数据,其类型为正整数和0,此时一个字节能存储的最大数是255。如果一个字节存储的是字符数据,也可以用正整数和0来代表,如ASCII码就是用0-255的数字代表256个字符,此时说存储的最大数为255也是合理的。

4、Question:关于一些基本类型的取之范围疑问,int类型(16位)的下溢下限为-32768而上溢上限却是32767

     上限:32767=2^15-1 (因为要有一个符号位)

     下限:为什么不是  -2^15+1  ?

    4.1 说法1:

      MAX_INT + 1 = 1000 0000 0000 0000 0000 0000 0000 0000根据int有符号的定义,第一位是符号位,这样看来,这个数值就是 -0 ; 那么我们再来讨论这样一种场景,按照int的第一个位是符号位的说法 MIN_INT=1111 1111 1111 1111 1111 1111 1111 111,前面我们计算过 31位的全1 = 2^31 -1, 所以MIN_INT=-2^31 + 1. 这应该是正常思路看待int的最大值和最小值的场景.但是我们还有一个-0没有解决,我们按照之前定义的MIN_INT来计算一下MIN_INT-1 = -2^31 + 1 -1 = -2^31. 咦,我们去掉超过的1位,发现它竟然也是 1000 0000 0000 0000 0000 0000 0000 0000 和 -0的含义一样 ,所以我们要解决的-0 可以把原来的MIN_INT-1 作为新的MIN_INT! 这样int的取值范围就是 -2^31 到 2^31 -1

 

    4.2 说法2:

       网上的解释是:
有+0和-0,两个0。因为负数在计算机中都是以补码方式存储的,且没有任何正数的补码是 1000 0000 0000 0000 0000 0000 0000 0000 ,所以把 -0 看成 int最小值(符号位参与运算)
 

      也就是说,为了解决冲突,类似于  补码中 1000 0000 表示 -128 (第一位符号位)其实是人为规定的

java低级运算-位运算

上一篇:机器学习基础


下一篇:SpringMVC声明式事务