Java编程的逻辑 (4) – 整数的二进制表示与位运算

Java编程的逻辑 (4) – 整数的二进制表示与位运算

本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接http://item.jd.com/12299018.html

Java编程的逻辑 (4) - 整数的二进制表示与位运算


上节我们提到正整数相乘的结果居然出现了负数,要理解这个行为,我们需要看下整数在计算机内部的二进制表示。

十进制

要理解整数的二进制,我们先来看下熟悉的十进制。十进制是如此的熟悉,我们可能已忽略了它的含义。比如123,我们不假思索就知道它的值是多少。

但其实123表示的1*(10^2) + 2*(10^1) + 3*(10^0),(10^2表示10的二次方),它表示的是各个位置数字含义之和,每个位置的数字含义与位置有关,从右向左,第一位乘以10的0次方, 即1,第二位乘以10的1次方,即10,第三位乘以10的2次方,即100,依次类推。

换句话说,每个位置都有一个位权,从右到左,第一位为1,然后依次乘以10,即第二位为10,第三位为100,依次类推。

正整数的二进制表示

正整数的二进制表示与此类似, 只是在十进制中,每个位置可以有10个数字,从0到9,但在二进制中,每个位置只能是0或1。位权的概念是类似的,从右到左,第一位为1,然后依次乘以2,即第二位为2,第三位为4,依次类推。

看一些数字的例子吧:

二进制 十进制
10 2
11 3
111 7
1010 10

负整数的二进制表示

十进制的负数表示就是在前面加一个负数符号-,例如-123。但二进制如何表示负数呢?

其实概念是类似的,二进制使用最高位表示符号位,用1表示负数,用0表示正数。

但哪个是最高位呢?整数有四种类型,byte/short/int/long,分别占1/2/4/8个字节,即分别占8/16/32/64位,每种类型的符号位都是其最左边的一位。

为方便举例,下面假定类型是byte,即从右到左的第8位表示符号位。

但负数表示不是简单的将最高位变为1,比如说:

  • byte a = -1,如果只是将最高位变为1,二进制应该是10000001,但实际上,它应该是11111111。
  • byte a=-127,如果只是将最高位变为1,二进制应该是11111111,但实际上,它却应该是10000001。 

和我们的直觉正好相反,这是什么表示法?这种表示法称为补码表示法,而符合我们直觉的表示称为原码表示法,补码表示就是在原码表示的基础上取反然后加1。取反就是将0变为1,1变为0。

负数的二进制表示就是对应的正数的补码表示,比如说:

  • -1:1的原码表示是00000001,取反是11111110,然后再加1,就是11111111。
  • -2:2的原码表示是00000010,取反是11111101,然后再加1,就是11111110。
  • -127:127的原码表示是01111111,取反是10000000,然后再加1,就是10000001。 

给定一个负数二进制表示,要想知道它的十进制值,可以采用相同的补码运算。比如:10010010,首先取反,变为01101101,然后加1,结果为01101110,它的十进制值为110,所以原值就是-110。直觉上,应该是先减1,然后再取反,但计算机只能做加法,而补码的一个良好特性就是,对负数的补码表示做补码运算就可以得到其对应整数的原码,正如十进制运算中负负得正一样。

byte类型,正数最大表示是01111111,即127,负数最小表示(绝对值最大)是10000000,即-128,表示范围就是 -128到127。其他类型的整数也类似,负数能多表示一个数。

负整数为什么采用补码呢?

负整数为什么要采用这种奇怪的表示形式呢?原因是:只有这种形式,计算机才能实现正确的加减法。

计算机其实只能做加法,1-1其实是1+(-1)。如果用原码表示,计算结果是不对的。比如说:

1  -> 00000001
-1 -> 10000001
+ ——————
-2 -> 10000010

用符合直觉的原码表示,1-1的结果是-2。

如果是补码表示:

1  -> 00000001
-1 -> 11111111
+ ——————
0  ->  00000000 

结果是正确的。

再比如,5-3:

5  -> 00000101
-3 -> 11111101
+ ——————
2  ->  00000010 

结果也是正确的。

就是这样的,看上去可能比较奇怪和难以理解,但这种表示其实是非常严谨和正确的,是不是很奇妙?

理解了二进制加减法,我们就能理解为什么正数的运算结果可能出现负数了。当计算结果超出表示范围的时候,最高位往往是1,然后就会被看做负数。比如说,127+1:

127   -> 01111111
1     -> 00000001
+ ——————
-128  -> 10000000 

计算结果超出了byte的表示范围,会被看做-128。

十六进制

二进制写起来太长,为了简化写法,可以将四个二进制位简化为一个0到15的数,10到15用字符A到F表示,这种表示方法称为16进制,如下所示:

2进制 10进制 16进制
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F

可以用16进制直接写常量数字,在数字前面加0x即可。比如10进制的123,用16进制表示是0x7B,即123 = 7*16+11。给整数赋值或者进行运算的时候,都可以直接使用16进制,比如:

int a = 0x7B;

Java中不支持直接写二进制常量,比如,想写二进制形式的11001,Java中不能直接写,可以在前面补0,补足8位,为00011001,然后用16进制表示,即 0x19。

查看整数的二进制和十六进制表示

在Java中,可以方便的使用Integer和Long的方法查看整数的二进制和十六进制表示,例如:

int a = 25;
System.out.println(Integer.toBinaryString(a)); //二进制
System.out.println(Integer.toHexString(a));  //十六进制
System.out.println(Long.toBinaryString(a)); //二进制
System.out.println(Long.toHexString(a));  //十六进制

位运算

位运算是将数据看做二进制,进行位级别的操作,Java不能单独表示一个位,但是可以用byte表示8位,可以用16进制写二进制常量。比如: 0010表示成16进制是 0x2, 110110表示成16进制是 0x36。

位运算有移位运算和逻辑运算。

移位有:

  • 左移:操作符为<<,向左移动,右边的低位补0,高位的就舍弃掉了,将二进制看做整数,左移1位就相当于乘以2。
  • 无符号右移:操作符为>>>,向右移动,右边的舍弃掉,左边补0。
  • 有符号右移:操作符为>>,向右移动,右边的舍弃掉,左边补什么取决于原来最高位是什么,原来是1就补1,原来是0就补0,将二进制看做整数,右移1位相当于除以2。

例如:

int a = 4; // 100
a = a >> 2; // 001,等于1
a = a << 3 // 1000,变为8

逻辑运算有:

  • 按位与 &:两位都为1才为1
  • 按位或 |:只要有一位为1,就为1
  • 按位取反 ~: 1变为0,0变为1
  • 按位异或 ^ :相异为真,相同为假

大部分都比较简单,就不详细说了。具体形式,例如:

int a = ...; 
a = a & 0x1 // 返回0或1,就是a最右边一位的值。
a = a | 0x1 //不管a原来最右边一位是什么,都将设为1

小结

本节我们讨论了整数的二进制表示,需要注意的就是负数的二进制表示,以及计算机进行二进制加减操作的过程,从而我们就能理解为什么有的时候正整数计算会出现负数。

我们同样讨论了整数的位运算,需要注意的就是无符号右移和有符号右移的区别。

理解了整数,那小数呢?

—————- 

未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),深入浅出,老马和你一起探索Java编程及计算机技术的本质。原创文章,保留所有版权。

Java编程的逻辑 (4) - 整数的二进制表示与位运算

———–

更多相关原创文章

计算机程序的思维逻辑 (1) – 数据和变量

计算机程序的思维逻辑 (2) – 赋值

计算机程序的思维逻辑 (3) – 基本运算

计算机程序的思维逻辑 (5) – 小数计算为什么会出错?

计算机程序的思维逻辑 (6) – 如何从乱码中恢复 (上)?

计算机程序的思维逻辑 (7) – 如何从乱码中恢复 (下)?

计算机程序的思维逻辑 (8) – char的真正含义

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/109063.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • chrome加载慢_多线程有什么用

    chrome加载慢_多线程有什么用谷歌浏览器采用的是单线程下载,想要提高下载速度,就得采用多线程的下载方式,Chrome默认还是单线程下载,如果想要谷歌多线程下载,就要手动开启,下面听MacW小编娓娓道来,介绍如何开启Chrome多线程下载!先来看看开启前的下载速度,(同一个文件)看到了吧!默认情况下,只有左右,远没有到达带宽的上限,接下来跟着小编一起开启这个隐藏的功能,国产Chrome内核的浏览器通通适用,包括前段时间推送的新版Edge也可以。chrome://flags/#enable-parallel-downloading

    2022年10月8日
    2
  • 模糊数学基础_模糊数学及其应用

    模糊数学基础_模糊数学及其应用模糊数学基础文章目录模糊数学基础1.前言2.区分随机性和模糊性3.模糊数学的基本概念1.模糊集和隶属函数2.模糊集的表示3.确定隶属函数的方法4.与传统集合论的区分4.模糊数学的基本运算1.模糊集的运算2.模糊关系与运算1.关系与模糊关系2.模糊关系矩阵的运算3.python程序求解法1.前言1965年美国著名控制论专家发表了FuzzySets从而开创了模糊数学的基本概念用“隶属度”和“隶属函数”来描述差异的中间过渡,处理和刻画模糊现象.处理现实现象的数学模型可以分为三

    2025年8月4日
    4
  • 记忆化搜索简介「建议收藏」

    记忆化搜索简介「建议收藏」记忆化搜索:算法上依然是搜索的流程,但是搜索到的一些解用动态规划的那种思想和模式作一些保存。一般说来,动态规划总要遍历所有的状态,而搜索可以排除一些无效状态。更重要的是搜索还可以剪枝,可能剪去大量不必要的状态,因此在空间开销上往往比动态规划要低很多。记忆化算法在求解的时候还是按着自顶向下的顺序,但是每求解一个状态,就将它的解保存下来,以后再次遇到这个状态的时候,就不必重新求解了。

    2022年7月26日
    7
  • saga分布式事务_分布式事务原理

    saga分布式事务_分布式事务原理saga是分布式事务领域里一个非常重要的事务模式,特别适合解决出行订票这类的长事务,本文将深度剖析saga事务的设计原理,以及在解决订票问题上的最佳实践01.saga的理论来源saga这种事务模式最早来自这篇论文:sagas在这篇论文里,作者提出了将一个长事务,分拆成多个子事务,每个子事务有正向操作Ti,反向补偿操作Ci。假如所有的子事务Ti依次成功完成,全局事务完成假如子事务Ti失败,那么会调用Ci,Ci-1,Ci-2….进行补偿论文阐述了上述这部分基本的saga逻辑之后

    2022年9月15日
    3
  • linux 主机支持远程唤醒_Linux远程开机

    linux 主机支持远程唤醒_Linux远程开机一,什么情况下需要远程开机?如果我们的服务器没有部署在本地(实际上通常都是这样的,我们会把服务器托管到IDC机房),而且服务器在机房中不止一台,其中一台被关闭时,则我们可以远程连接一台没有关机的服务器上,然后进行远程开机.二,远程开机需要的软件它需要wakeonlan这个软件,从何处得到它?它的官方站是:http://sourceforge.net/projects/wake-on-lan/如果使…

    2022年5月5日
    100
  • conda换源后没用_打开电视默认信号源

    conda换源后没用_打开电视默认信号源最近由于某些因素清华的conda镜像登不上去了,所以需要换回conda的默认源。查看了condaconfig的文档后,发现直接删除channels即可。命令如下:condaconfig–remove-keychannels

    2022年9月26日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号