码迷,mamicode.com
首页 > 编程语言 > 详细

跟光磊学Java开发-深入理解整数存储和位运算

时间:2020-12-17 12:49:54      阅读:1      评论:0      收藏:0      [点我收藏+]

标签:表示   comment   orm   结果   16px   white   系统   wrap   电路   

跟光磊学Java开发-深入理解整数存储和位运算

计算机进制及其转换

计算机进制介绍

进制的定义:进制是一种计数方式,也称为进位计数法或者位值计数法,使用有限数字符号表示无限的数值,使用的数字符号的数目称为这种进位制的基数或者底数,例如十进制就是由0-9十个数字组成。在计算机内存中,都是以二进制的补码形式来存储数据的,生活中以十进制方式计算的数据居多,例如账户余额,开发人员的薪水等等。而 计算的内存地址、MAC地址等等通常都是使用十六进制表示的,Linux的权限系统采用八进制的数据表示的。相同进制类型数据进行运算时会遵守加法:逢R进1;减法:**借1当R,**其中R就表示进制。

计算机常用进制的组成、示例和使用场景

进制名称 组成 数值示例 应用场景
二进制 0,1 1010 计算机内部数据表示
八进制 0,1,2,3,4,5,6,7 010 Linux权限系统
十进制 01,2,3,4,5,6,7,8,9 12 整数
十六进制 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f 12f 数据的内存地址

十进制、二进制、八进制、十六进制的对应关系

十进制 二进制 八进制 十六进制
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 A
11 1011 13 B
12 1100 14 C
13 1101 15 D
14 1110 16 E
15 11111 17 F
16 10000 20 10

计算机底层为什么只能识别二进制

生活中使用的是十进制,而计算机采用的二进制,但是由于用二进制表示数据太长,不方便阅读,因此引入了八进制、十六进制。

我们目前主要使用的计算机都是大规模集成电路,是采用大规模和超大规模的集成电路作为逻辑元件的。集成电路按其功能、结构的不同,可以分为模拟集成电路、数字集成电路和数/模混合集成电路三大类。而我们的计算机主要是采用数字集成电路搭建的。逻辑门是数字逻辑电路的基本单元。常见的逻辑门包括“与”门,“或”门,“非”门,“异或”等等。通过逻辑门可以组合使用实现更为复杂的逻辑运算和数值运算。逻辑门可以通过控制高、低电平,从而实现逻辑运算。电源电压大小的波动对其没有影响,温度和工艺偏差对其工作的可靠性影响也比模拟电路小得多,所以相对稳定。因为数字计算机是由逻辑门组成,而逻辑电路最基础的状态就是两个:开和关。所以,数字电路是以二进制逻辑代数为数学基础。二进制的基本运算规则简单,运算操作方便,这样一来有利于简化计算机内部结构,提高运算速度。但是在日常开发中,通常都会使用八进制和十六进制,因为八进制和十六进制相对于二进制表示数据更加简洁,而且一个八进制表示三个二进制,一个十六进制表示四个二进制。例如1024使用二进制表示为0b100 0000 0000,使用八进制表示为02000,使用十六进制表示为0x400。

十进制转二进制、八进制、十六进制

十进制转换二进制、八进制、十六进制可以采用短除法,即待转换的十进制数除以指定的进制(例如2,8,16),直到商数为0,求余数。

十进制101转换为二进制的计算过程

重复除以2 商数 余数
101/2 50 1
50/2 25 0
25/2 12 1
12/2 6 0
6/2 3 0
3/2 1 1
1/2 0 1

然后将余数的结果从下到上串联起来的结果:1100101,即十进制的101转换为二进制的结果为1100101

十进制的237转换为二进制

重复除以2 商数 余数
237/2 118 1
118/2 59 0
59/2 29 1
29/2 14 1
14/2 7 0
7/2 3 1
3/2 1 1
1/2 0 1

然后将余数的结果从下到上串联起来的结果:11101101,即十进制的237转换为二进制的结果为11101101。

除了短除法以外,如果掌握了常见的十进制数对应的二进制数

科学计数法 十进制 二进制
2^0 1 1
2^1 2 10
2^2 4 100
2^3 8 1000
2^4 16 10000
2^5 32 100000
2^6 64 1000000
2^7 128 10000000
2^8 256 100000000
2^9 512 1000000000
2^10 1024 10000000000

在进行十进制转二进制时,还可以使用减法来转换
以十进制11转换为二进制为例,因为11介于16和8中间,而11-8=3,8的二进制表示为1000,3的二进制是11,而二进制的1000+11=1011,因此十进制的11转换为二进制的结果是1011

Windows系统中可以使用计算器(calc)来实现进制之间的转换
技术图片

二进制、八进制、十六进制转十进制

首先明确不同进制的值是如何计算的,这里以十进制和二进制为例子,阐述它们的计算过程。

十进制整数1024

1024=1*10^3+2*10^1+4*10^0=1000+20+4=1024

二进制整数 10000000000

10000000000 =1*2^10=1024

二进制、八进制、十六进制整数转十进制整数是使用按权展开法计算的,这里以二进制数据01100101为例子。从右往左开始数,如果二进制位为1,则依次用1*2^n,n从0开始,依次递增。

二进制整数01100101 转换为十进制整数的计算过程

01100101=2^6+2^5+2^2+2^0=64+32+4+1=101

八进制整数0127转换为十进制整数的计算过程

0127=1*8^2+2*8^1+7=64+16+7=87

十六进制整数0x12f转换为十进制整数的计算过程

0x12f=1*16^2+2*16^1+f*16^0=256+32+15=303

二进制的1011转换为十进制的计算过程

1011=1000+11=2^3+2^1+2^0=8+2+1=11

二进制转八进制、十六进制

二进制转八进制是按照从右往左,每3位二进制对应1位八进制,不足补零,因为2的3次方等于8

二进制整数11001100转八进制计算过程

11 001 100 =0314

二进制转十六进制是按照从右往左,每4位二进制对应1位十六进制,不足补零,因为2的4次方等于16。

二进制整数1100 1110转十六进制计算过程

1100 1110 =0xce

八进制、十六进制转二进制

八进制转二进制是按照从右往左,每1位八进制对应3位二进制。

八进制整数0127转二进制整数计算过程

0127=001 010 111

十六进制转二进制是按照从右往左,每1位十六进制对应4位二进制。

十六进制整数0x12f转换为二进制整数计算过程

0x12f=0001 0010 1111

Java的四种进制整数常量

Java支持二进制、八进制、十进制和十六进制四种禁止的整数常量

  • 二进制整数常量以0b开头,包含0和1两个数字组成
  • 八进制整数常量以0开头,包含0-7之间的八个数字组成
  • 十进制是整数常量的默认进制,包含0-9之间的十个数字组成
  • 十六进制以0x开头,包含0-9之间的十个数字以及a-f之间的6个字母组成

使用System.out.println()输出整数常量时默认都是按照十进制的结果输出

package net.ittimeline.java.core.jdk.foundational.syntax;

/**
 * 四种进制的整数常量
 *
 * @author tony 18601767221@163.com
 * @version 2020/12/12 7:47
 * @since JDK11
 */
public class IntConstants {

  public static void main(String[] args) {
    // 二进制常量使用0b开头,JDK7以后支持二进制常量
    System.out.println("二进制数据100转换为十进制的数据结果为" + 0b100);
    // 八进制整数 常量以0开头
    System.out.println("八进制数据100转换为十进制的数据结果为" + 0100);
    // 整数常量默认是十进制
    System.out.println("十进制数据100" + 100);
    // 十六进制整数常量以0x开头,包含0-9之间的十个数字以及a-f之间的6个字母组成
    System.out.println("十六进制数据0x100转换为十进制的数据结果为" + 0x100);
  }
}

程序运行结果
技术图片

整数存储和运算的机制

原码、反码、补码

任何数据在计算机中是以补码的方式存储,而数据的表现形式有原码、反码和补码。
正数的原码,反码和补码都一致,而负数经历了原码->反码->补码的换算过程

  • 原码
    原码表示数据本身的二进制
    例如无符号数15以一个字节的原码表示为0000 1111
    有符号 分为正数和负数,最高位表示符号位。正数15 以一个字节的原码表示为 00001111,而负数-15以一个字节的原码表示为1000 1111

  • 反码
    无符号数 反码等于原码,以一个字节的无符号整数15为例,其原码为0000 1111,其反码也是0000 1111
    有符号数 正数反码等于原码,负数就是最高位不变,其他位取反,以-15为例,其原码为 1000 1111,反码表示为1111 0000

  • 补码
    无符号数 补码等于原码
    有符号数 正数补码等于原码,负数就是反码的最低位加1即可,以-15为例,原码为 1000 1111 ,反码表示为1111 0000,补码就是反码加1,其结果是1111 0001。

综上所述,无符号数以及有符号正数计算机存的是原码,因为补码等于原码。而有符号负数计算机存储的是补码,补码等于原码取反加1。

原码->反码->补码的演变过程(以byte类型说明)

首先明确 原码的+0,+1和-0,-1表示方法
0的原码是0000 0000 ,1的原码是0000 0001
-0的原码是1000 0000, -1的原码是1000 0001

无符号正数,有符号正数的原码,反码和补码都是一样的

为什么有了原码还需要补码,这里以原码计算1-1,因为计算器内部只有加法器,因为1-1 等价于1+ -1,因此
0000 0001+ 1000 0001=1000 0010 转换成十进制 等于-2 ,证明使用原码存储负数,在参与运算时得到的确是错误的结果。
除此以外,使用原码表示0有两种表示方法:0000 0000 和1000 0000

因为原码的负数运算结果错误,而且不能正确的表示0,因此计算机行业的大佬们开始尝试使用反码来存储负数

首先明确 反码的+0,+1和-0,-1表示方法
0的原码是0000 0000,转换为反码是0000 0000 ,1的原码是0000 0001,反码是0000 0001
-0的原码是1000 0000,转换为反码是1111 1111, -1的原码是1000 0001,反码是1111 1110

使用反码计算 1+ -1 = 0000 0001+1111 1110=1111 1111 ,1111 1111刚好是-0的反码,虽然负数使用反码运算时,结果准确,但是0的表示方式依然还是有两种:0000 0000 和1111 1111。

综上所述 如果计算机使用反码存储负数,运算准确,但是0的反码表示方式还是有两种。

由于反码不能正确表示0的方式,计算机大佬们又开始想新的方式:补码来存储负数

首先明确 补码的+0,+1和-0,-1表示方法
0的原码是0000 0000,转换为反码是0000 0000,转换为补码是0000 0000 ,1的原码是0000 0001,反码是0000 0001,转换为补码是0000 0000
-0的原码是1000 0000,转换为反码是1111 1111,转换为补码为0000 0000, -1的原码是1000 0001,反码是1111 1110,转换为补码是1111 1111

使用补码计算 1+ -1= 0000 0001 + 1111 1111=0000 0000 转换为十进制的结果就是0,而且补码+0和-0的二进制表示方式都是0000 0000,因此补码即解决了负数运算时的结果正确性,又保证了0只有一种0000 0000的表示方式。

这里再总结下原码、反码、补码三者之间的计算。

  1. 根据原码求补码
    根据原码求补码之前首先需要根据原码求反码,即原码的最高位不变,其他位取反。求到反码以后,就可以根据反码的末位加1得到补码。
  2. 根据补码求原码
    根据补码求原码之前首先需要根据补码求反码,即补码末位减1得到反码,然后反码求原码,也就是反码符号位不变,其他位取反得到原码。

其实这里就可以看出反码的作用,它就是用于原码和补码转换的桥梁。
而补码的使用场景是计算机数据在进行相关运算时(例如算术运算,赋值运算,位运算)使用的。
原码的使用场景是计算技术数据在进行展示时(例如使用System.out.println("");打印输出)使用的。

为什么byte类型的范围是-128到127?

因为byte占据的字节数量是1个字节(Byte),1Byte=8bit,即最小值是0000 0000,最大值是1111 1111,但是有符号整数由正整数、0和负整数三部分组成。
正整数0的一个字节表示方式为0000 0000
一个字节表示最小的正整数 0000 0001
一个字节表示最大的正整数 0111 1111 换算成十进制就是127
一个字节表示最小的负整数 1000 0001 ,此时1000 0001是补码,还需要转换为反码和原码,1000 0001 转换为反码的结果是1000 0000,1000 0000转换为原码的结果是1111 1111,1111 1111转换为十进制就是-127
一个字节表示最大的负整数1111 11111,此时1111 1111是补码,还需要转换为反码和原码,11111111 转换为反码的结果是1111 1110,1111 1110转换为原码的结果就是 1000 0001,1000 0001转换为十进制就是-1。
负整数0的一个字节表示方式为1000 0000,如果用1000 0000表示负0,那就浪费了,因为0不区分正负,因此用1000 0000表示-128。
1000 0000 怎么计算是-128呢?
-127的二进制补码是1000 0001 ,1的二进制补码是0000 0001,-127 - 1=-128,因此 1000 0001 - 0000 0001 =1000 0000=-128。

整数溢出的内存原理

在使用对应数据类型时不要超过它们的范围,如果超过了就会造成数据溢出错误,执行程序会得到一个错误的结果。

package net.ittimeline.java.core.jdk.foundational.syntax;

/**
 * 整数溢出的内存原理
 *
 * @author tony 18601767221@163.com
 * @version 2020/12/12 8:36
 * @since JDK11
 */
public class IntOverFlow {
  public static void main(String[] args) {
      //赋值时(修改变量的值) 赋的值是十进制,给的是原码,如果赋值给的是八进制或者十六进制,给的是补码
      //打印输出到终端(获取变量的值)十进制打印的是原码,十六进制或者八进制打印输出的是补码

      /*

	    数据溢出的内存原理
		129默认是int类型,但是在赋值之前使用了强制类型转换转换为byte类型了
		1Byte=8bit
		129使用1个字节的补码表示的方式是 1000 0001

		在打印输出时需要转换成原码
		1000 0001 首先转换成反码
		补码转反码就是补码的末位减1 也就是1000 0001 -1 =1000 0000
		反码转换成原码 最高位不变 其他位取反
		反码1000 0000 转换成原码的结果是1111 1111
		打印输出时默认是有符号的十进制输出,因此打印输出结果是-127

	*/

      byte number=(byte)129;
    System.out.println("number = "+number);
  }
}

程序运行结果

技术图片
程序运行结果

整数赋值溢出的原因剖析

赋值时(修改变量的值) 赋的值是十进制,给的是原码,如果赋值给的是八进制或者十六进制,给的是补码
打印输出到终端(获取变量的值)十进制打印的是原码,十六进制或者八进制打印输出的是补码

129默认是int类型,占据4个字节,也就是32个二进制位,但是在赋值之前使用了强制类型转换转换为byte类型了
1Byte=8bit,也就只能用8个二进制位来表示129
129使用1个字节的补码表示的方式是 1000 0001
在打印输出时需要转换成原码
1000 0001 首先转换成反码
补码转反码就是补码的末位减1 也就是1000 0001 -1 =1000 0000
反码转换成原码 最高位不变 其他位取反
反码1000 0000 转换成原码的结果是1111 1111
打印输出时默认是有符号的十进制输出,因此打印输出结果是-127

位运算

位运算符概述

位运算是整数基于二进制补码的运算,其运算效率是最高的,但是可读性不太好。因为人们生活中接触十进制比较多,而计算机底层都是二进制的。
位运算符在进行运算时先将十进制数转换成二进制再进行运算。
Java语言持7种位运算符,包括四种基本位运算符:按位与(&),按位或(|),按位异或(^)和按位取反(~)和三种移位运算符:按位左移(<<),按位右移(>>)以及无符号右移(>>>)

在Java源码中大量使用了基于二进制补码的位运算,但是在进行业务开发时很少用位运算符。
三种移位运算符在JDK集合源码中的使用
无符号右移
技术图片
按位右移
技术图片
按位左移
技术图片

如何区分&,|,^是逻辑运算符还是位运算符? 如果操作的是boolean类型就是逻辑运算符,如果操作的是整数,那么就是位运算符

四种基本位运算符的使用

  • 按位与(&):只有当&两边的二进制补码都为1时返回1,其余返回0
    • 1&1=1
    • 1&0=0
    • 0&1=0
    • 0&0=0

按位与运算案例:255&15

255和15都是正整数 正整数原码、反码、补码都一样
255默认是十进制,占据4个字节长度,转换为二进制的结果是 0000 0000 0000 0000 0000 0000 1111 1111
15默认是十进制,占据4个字节长度,转换为二进制的结果是 0000 0000 0000 0000 0000 0000 0000 1111
1111 1111 & 0000 1111 =0000 1111,转换为十进制的结果是 15

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位与运算案例:255&15
 *
 * @author tony 18601767221@163.com
 * @version 2020/12/12 11:01
 * @since JDK11
 */
public class BitAndInt {

  public static void main(String[] args) {
    /*
        255和15都是正整数 正整数原码、反码、补码都一样

        255默认是十进制,占据4个字节长度
        转换为二进制的结果是 0000 0000 0000 0000 0000 0000 1111 1111

        15默认是十进制,占据4个字节长度
        转换为二进制的结果是 0000 0000 0000 0000 0000 0000 0000 1111

        1111 1111
   按位与(&)
        0000 1111
   =
        0000 1111
   转换为十进制的结果是 15
     */

    System.out.println("255 & 15 = "+(255&15));
  }
}

程序运行结果
技术图片

  • 按位或(|):当|两边的二进制补码有一边为1时返回1,其余返回0
    • 1 | 1=1
    • 1| 0=1
    • 0 | 1=1
    • 0 | 0=0

按位或运算案例: 128|64

128和64都是正整数 正整数原码、反码、补码都一样
128默认是十进制,占据4个字节长度,转换为二进制的结果是 0000 0000 0000 0000 0000 0000 1000 0000
64默认是十进制,占据4个字节长度,转换为二进制的结果是 0000 0000 0000 0000 0000 0000 0100 0000
1000 0000 | 0100 0000=1100 0000 ,转换为十进制的结果是192

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位或运算案例 128&64
 *
 * @author tony 18601767221@163.com
 * @version 2020/12/12 11:13
 * @since JDK11
 */
public class BitOrInt {

  public static void main(String[] args) {
     /*
        128和64都是正整数 正整数原码、反码、补码都一样

        128默认是十进制,占据4个字节长度
        转换为二进制的结果是 0000 0000 0000 0000 0000 0000 1000 0000

        64默认是十进制,占据4个字节长度
        转换为二进制的结果是 0000 0000 0000 0000 0000 0000 0100 0000

        1000 0000
   按位或(|)
        0100 0000
   =
        1100 0000
   转换为十进制的结果是 192
     */

      System.out.println("128 | 64 = "+(128|64));
  }
}
  • 按位异或():当两边的二进制补码相同时返回0,不同时返回1
    • 1 ^ 1=0
    • 1 ^ 0=1
    • 0 ^ 1=1
    • 0 | 0 = 0

按位异或案例:256^256

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位异或运算案例 256&256
 *
 * @author tony 18601767221@163.com
 * @version 2020/12/12 11:20
 * @since JDK11
 */
public class BitXorInt {

  public static void main(String[] args) {
     /*
        256是正整数 正整数原码、反码、补码都一样

        256默认是十进制,占据4个字节长度
        转换为二进制的结果是 0000 0000 0000 0000 0000 0001 0000 0000



        1 0000 0000
   按位与(|)
        1 0000 0000
   =
        0 0000 0000
   转换为十进制的结果是 0
     */

      System.out.println("256 ^ 256 = "+(256 ^ 256));
  }
}

程序运行结果
技术图片

在进行异或运算时一个数被另一个数异或两次,该数本身不变,例如10^5^5=10

因此异或可以拿来做变量交换,而且是最优解的算法。

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 使用数据交换
 *
 * @author tony 18601767221@163.com
 * @version 2020/12/12 11:30
 * @since JDK11
 */
public class BitXorDataExchange {

  public static void main(String[] args) {

    int left = 10;
    int right = 20;
    System.out.printf("【异或运算】变量交换之前 left = %d  right = %d \n", left, right);

    left = left ^ right;
    right = left ^ right;
    left = left ^ right;
    System.out.printf("【异或运算】变量交换之后 left = %d  right = %d \n", left, right);
  }
}

技术图片
程序运行结果

其他两种交换数据的方法就是通过临时变量以及算术运算。

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 数据交换的其他两种方式
 *
 * @author tony 18601767221@163.com
 * @version 2020/12/12 11:33
 * @since JDK11
 */
public class DataExchange {

  public static void main(String[] args) {

      int left= 10;
      int right=20;

      dataExchangeArithmetic(left,right);

      dataExchangeTempSpace(left,right);
      
  }

    /**
     * 使用算术运算交换两个变量
     * @param left
     * @param right
     */
  public static void dataExchangeArithmetic(int left,int right){
      System.out.printf("【算术运算】变量交换之前 left = %d  right = %d \n", left, right);
      left=left+right;
      right=left-right;
      left=left-right;
      System.out.printf("【算术运算】变量交换之后 left = %d  right = %d \n", left, right);

  }

    /**
     * 使用临时变量交换两个变量
     * @param left
     * @param right
     */
    public static void dataExchangeTempSpace(int left,int right){
        System.out.printf("【临时空间】变量交换之前 left = %d  right = %d \n", left, right);
        int temp=left;
        left=right;
        right=temp;
        System.out.printf("【临时空间】变量交换之后 left = %d  right = %d \n", left, right);

    }



}

程序运行结果
技术图片

  • 按位取反(~) :单目运算符,即只能操作一个变量或则表达式,将操作数的每个位(包括符号位)全部取反
    • ~0=1
    • ~1=0

取反运算符案例: ~245 和~-245

~245的计算过程
技术图片

~-245的计算过程
技术图片

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 按位取反运算案例:~245
 * 按位取反(~) :单目运算符,即只能操作一个变量或则表达式,将操作数的每个位(包括符号位)全部取反
 * @author tony 18601767221@163.com
 * @version 2020/12/12 12:24
 * @since JDK11
 */
public class BitNotSignedInt {
    public static void main(String[] args) {

        /*
         ~245的计算过程
          245的二进制补码是0000 0000 0000 0000 0000 0000 1111 0101

          按位取反(包括符号位)
                         1111 1111 1111 1111 1111 1111 0000 1010
          取反的补码结果是一个负数需要将其转换成原码输出
          其反码表示方式是  1111 1111 1111 1111 1111 1111 0000 1001
          其原码表示方式是  1000 0000 0000 0000 0000 0000 1111 0110
          转换为十进制的结果是-246
         */

        System.out.println("十进制有符号正整数245按位取反的结果是"+(~245));

        /*
        ~-245的计算过程
          -245的二进制原码是1000 0000 0000 0000 0000 0000 1111 0101
          其反码表示方式    1111 1111 1111 1111 1111 1111 0000 1010
          其补码表示方式    1111 1111 1111 1111 1111 1111 0000 1011
          按位取反(包括符号位)
                         0000 0000 0000 0000 0000 0000 1111 0100
          取反的补码结果是一个正整数,转换为十进制的结果是244
         */

        System.out.println("十进制有符号负整数-245按位取反的结果是"+(~-245));



    }
}

程序运行结果
技术图片

三种移位运算的使用

  • 有符号左移(<<) :二进制位向左移动,左边符号位丢弃,右边补齐0。有符号左移的规律:向左移动几位,就是乘以2的几次方

有符号左移案例: 10<<2和-10<<2

10<<2 计算过程
技术图片
-10<<2计算过程

技术图片
十进制有符号正整数左移


package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 有符号左移案例: 10<<2和-10<<2
 * 正数、负数的按位左移
 * 二进制位向左移动,左边符号位丢弃,右边补齐0。有符号左移的规律:向左移动几位,就是乘以2的几次方
 * @author tony 18601767221@163.com
 * @version 2020/12/12 11:38
 * @since JDK11
 */
public class BitLeftMoveSignedInt {
  public static void main(String[] args) {

      /*
       10 << 2的计算过程
       10默认是有符号的十进制正整数
       转换为二进制 0000 0000 0000 0000 0000 0000 0000 1010
       向左边移动两位后 右边补两个0 左边的两个0丢弃
       00 0000 0000 0000 0000 0000 0000 101000
       101000 转换为十进制的结果是40
       因此 10 << 2 =40
       */
      System.out.println("有符号十进制正整数10左移2位的结果是" + (10 << 2));

      /*
      -10 << 2 的计算过程
      -10默认是有符号的十进制负整数
      -10原码是         1000 0000 0000 0000 0000 0000 0000 1010
      在进行移位运算之前需要转换为补码
      原码取反转换为反码  1111 1111 1111 1111 1111 1111 1111 0101
      反码加1转换为补码   1111 1111 1111 1111 1111 1111 1111 0110

      1111 1111 1111 1111 1111 1111 1111 0110 向左移动两位
      右边补上2个0,左边符号位丢弃
      11 1111 1111 1111 1111 1111 1111 011000
      输出时需要将其转换为原码
      补码 11 1111 1111 1111 1111 1111 1111 011000
      先计算11 1111 1111 1111 1111 1111 1111 011000的反码:即补码减1
      1111 1111 1111 1111 1111 1111 1101 1000 -1=1111 1111 1111 1111 1111 1111 1101 0100
      再求1111 1111 1111 1111 1111 1111 1101 0100原码:最高位不变,其他位取反
      1111 1111 1111 1111 1111 1111 1101 0100转换原码结果是1000 0000 0000 0000 0000 0000 0010 1000
      1000 0000 0000 0000 0000 0000 0010 1000 转换为十进制输出就是-40

       */
      System.out.println("有符号十进制负整数-10左移2位的结果是" + (-10 << 2));
  }
}

程序运行结果
技术图片

  • 有符号右移(>>):二进制位向右移动,使用符号位进行补位,符号位是1那就补上1,符号位是0就补0。有符号右移规律:向右移动几位,就是除以2的几次方

有符号整数的右移案例 32 >> 2,-32 >> 2

32 >> 2的计算过程
技术图片

-32 >> 2的计算过程
技术图片

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 有符号整数的右移案例 32 >> 2,-32 >> 2
 * 正整数和负整数的右移运算
 *  有符号右移(>>):二进制位向右移动,使用符号位进行补位,符号位是1那就补上1,符号位是0就补0。有符号右移规律:向右移动几位,就是除以2的几次方
 * @author tony 18601767221@163.com
 * @version 2020/12/12 12:02
 * @since JDK11
 */
public class BitRightMoveSignedInt {

  public static void main(String[] args) {

    /*
        32 >> 2 的计算过程
        整数常量默认是4个字节
        32是四个字节的有符号十进制整数
        其二进制补码表示方式为0000 0000 0000 0000 0000 0000 0010 0000

        0000 0000 0000 0000 0000 0000 0010 0000 向右移动2位,右边去掉2个0,符号位是1,左边补上2个0
        0000 0000 0000 0000 0000 0000 0000 1000
        1000 转换为十进制的结果是8

     */
    System.out.println("有符号十进制正整数32右移2位的结果是"+(32 >> 2));

    /*
        -32 >> 2 的计算过程
        整数常量默认是4个字节
        -32是四个字节的有符号十进制整数
        其二进制原码表示方式为1000 0000 0000 0000 0000 0000 0010 0000
        其二进制反码表示方式为1111 1111 1111 1111 1111 1111 1101 1111
        其二进制补码表示方式为1111 1111 1111 1111 1111 1111 1110 0000

        1111 1111 1111 1111 1111 1111 1110 0000 向右移动2位,右边去掉两个0,左边补上两个1
        右移两位的结果是1111 1111 1111 1111 1111 1111 1111 1000
        其二进制反码表示方式为 1111 1111 1111 1111 1111 1111 1111 0111
        其二进制原码表示方式为 1000 0000 0000 0000 0000 0000 0000 1000
        转换为十进制的结果是-8
     */
    System.out.println("有符号十进制正整数32右移2位的结果是"+(32 >> 2));


  }
}

程序运行结果
技术图片

  • 无符号右移(>>>):二进制位向右移动,无论符号位是0还是1,左边都补0。无符号右移,对于负数来说,右移后会变成正数。

有符号整数的无符号右移案例 188 <<< 2 和 -188 <<< 2

188 <<< 2的计算过程
技术图片

-188 <<< 2的计算过程
技术图片

package net.ittimeline.java.core.jdk.foundational.operator.bit;

/**
 * 有符号整数的无符号右移案例 188 <<< 2 和 -188 <<< 2
 * 无符号右移(>>>):二进制位向右移动,无论符号位是0还是1,左边都补0。无符号右移,对于负数来说,右移后会变成正数。
 * @author tony 18601767221@163.com
 * @version 2020/12/12 12:57
 * @since JDK11
 */
public class UnsignedBitRightMoveSignedInt {

  public static void main(String[] args) {

      /*
            188 <<< 2的计算过程
            188的二进制原码表示方式为 0000 0000 0000 0000 0000 0000 1011 1100
          向右边无符号移动2位的结果0000 0000 0000 0000 0000 0000 0010 1111
          10 1111转换为十进制47
       */

    System.out.println("有符号十进制正整数188的无符号右移2位的结果是"+ (188 >>> 2) );

    /*
        -188 <<< 2的计算过程
            -188的二进制原码表示方式为 1000 0000 0000 0000 0000 0000 1011 1100
            -188的二进制反码表示方式为 1111 1111 1111 1111 1111 1111 0100 0011
            -188的二进制补码表示方式为 1111 1111 1111 1111 1111 1111 0100 0100

          向右边无符号移动2位,去掉右边的两个0,左边补上两个0
         001111 1111 1111 1111 1111 1111 0100 01 转换为十进制的结果是1073741777

     */
    System.out.println("有符号十进制负整数-188无符号右移2位的结果是"+ (-188 >>> 2) );
  }
}

程序运行结果
技术图片

跟光磊学Java开发-深入理解整数存储和位运算

标签:表示   comment   orm   结果   16px   white   系统   wrap   电路   

原文地址:https://www.cnblogs.com/ittimeline/p/14124654.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!