资讯专栏INFORMATION COLUMN

一文搞明白位运算、补码、反码、原码

tunny / 2904人阅读

摘要:我们来看一个特殊的运算反码反码反码原码。补码为了解决反码的问题就出现了补码。用原码表示为用反码表示为用补码表示为,表示的补码左移一位后为,该补码对应的反码为该反码对应的原码为符号位不变,其他位取反,为,表示。

在平时看各种框架的源码的过程中,经常会看到一些位移运算,所以作为一个Java开发者是一定掌握位移运算的。

正数位移运算

Java中有三个位移运算:

<<:左移

>>:右移

>>>:无符号右移

我们直接看一下Demo:

System.out.println(2 << 1);     // 4
System.out.println(2 >> 1);     // 1
System.out.println(2 >>> 1);    // 1
System.out.println(-2 << 1);    // -4
System.out.println(-2 >> 1);    // -1
System.out.println(-2 >>> 1);   // 2147483647

乍一眼看到上面Demo的打印结果,你应该是懵逼的,接下来我来解释一下这个结果到底是如何运算出来的。

上面的Demo中有“2”和“-2”,这是两个十进制数,并且是int类型的(java中占四个字节),位运算是基于二进制bit来的,所以我们需要将十进制转换为二进制之后再进行运算

2 << 1:十进制“2”转换成二进制为“00000000 00000000 00000000 00000010”,再将二进制左移一位,高位丢弃,低位补0,所以结果为“00000000 00000000 00000000 00000100”,换算成十进制则为“4”

2 >> 1:十进制“2”转换成二进制为“00000000 00000000 00000000 00000010”,再将二进制右移一位,低位丢弃,高位补0,所以结果为“00000000 00000000 00000000 00000001”,换算成十进制则为“1”

对于这两种情况非常好理解,那什么是无符号右移,以及负数是怎么运算的呢?

我们先来看-2 << 1-2 >> 1,这两个负数的左移与右移操作其实和正数类似,都是先将十进制数转换成二进制数,再将二进制数进行移动,所以现在的关键是负数如何用二进制数进行表示。

原码、反码、补码

杰西莱我们主要介绍十进制数用二进制表示的不同方法,所以为了简洁,我们用一个字节,也就是8个bit来表示二进制数。

原码
十进制 原码
2 0000 0010
-2 1000 0010

原码其实是最容易理解的,只不过需要利用二进制中的第一位来表示符号位,0表示正数,1表示负数,所以可以看到,一个数字用二进制原码表示的话,取值范围是-111 1111 ~ +111 1111,换成十进制就是-127 ~ 127

反码

在数学中我们有加减乘除,而对于计算机来说最好只有加法,这样计算机会更加简单高效,我们知道在数学中5-3=2,其实可以转换成5+(-3)=2,这就表示减法可以用加法表示,而乘法是加法的累积,除法是减法的累积,所以在计算机中只要有加法就够了。

一个数字用原码表示是容易理解的,但是需要多带带的一个bit来表示符号位。并且在进行加法时,计算机需要先识别某个二进制原码是正数还是负数,识别出来之后再进行相应的运算。这样效率不高,能不能让计算机在进行运算时不用去管符号位,也就是说让符号位也参与运算,这就要用到反码。

十进制 原码 反码
2 0000 0010 0000 0010
-2 1000 0010 1111 1101

正数的反码和原码一样,负数的反码就是在原码的基础上符号位保持不变,其他位取反。

那么我们来看一下,用反码直接运算会是什么情况,我们以5-3举例。

5 - 3 等于 5 + (-3)

十进制 原码 反码
5 0000 0101 0000 0101
-3 1000 0011 1111 1100

   

  5-3
= 5+(-3)
= 0000 0101(反码) + 1111 1100(反码) 
= 0000 0001(反码)
= 0000 0001(原码) 
= 1。

这不对呀?!! 5-3=1?,为什么差了1?

我们来看一个特殊的运算:

  1-1
= 1+(-1)
= 0000 0001(反码) + 1111 1110(反码)
= 1111 1111(反码)
= 1000 0000(原码)
= -0。

我们来看一个特殊的运算:

  0+0
= 0000 0000(反码) + 0000 0000(反码)
= 0000 0000(反码)
= 0000 0000(原码)
= 0。

我们可以看到1000 0000表示-0,0000 0000表示0,虽然-0和0是一样的,但是在用原码和反码表示时是不同的,我们可以理解为在用一个字节表示数字取值范围时,这些数字中多了一个-0,所以导致我们在用反码直接运算时符号位可以直接参加运算,但是结果会不对。

补码

为了解决反码的问题就出现了补码。

十进制 原码 反码 补码
2 0000 0010 0000 0010 0000 0010
-2 1000 0010 1111 1101 1111 1110

正数的补码和原码、反码一样,负数的补码就是反码+1。

十进制 原码 反码 补码
5 0000 0101 0000 0101 0000 0101
-3 1000 0011 1111 1100 1111 1101
  5-3
= 5+(-3)
= 0000 0101(补码) + 1111 1101(补码)
= 0000 0010(补码)
= 0000 0010(原码) 
= 2。

5-3=2!!正确。

再来看特殊的: 

  1-1
= 1+(-1)
= 0000 0001(补码) + 1111 1111(补码)
= 0000 0000(补码)
= 0000 0000(原码)
= 0。

1-1=0!!正确

再来看一个特殊的运算:

  0+0
= 0000 0000(补码) + 0000 0000(补码)
= 0000 0000(补码)
= 0000 0000(原码)
= 0。

0+0=0!!也正确。

所以,我们可以看到补码解决了反码的问题。

所以对于数字,我们可以使用补码的形式来进行二进制表示。

负数位移运算

我们再来看-2 << 1-2 >> 1
-2用原码表示为10000000 00000000 00000000 00000010
-2用反码表示为11111111 11111111 11111111 11111101
-2用补码表示为11111111 11111111 11111111 11111110
-2 << 1,表示-2的补码左移一位后为11111111 11111111 11111111 11111100,该补码对应的反码为  

  11111111 11111111 11111111 11111100
- 1
= 11111111 11111111 11111111 11111011

该反码对应的原码为:符号位不变,其他位取反,为10000000 00000000 00000000 00000100,表示-4。
所以-2 << 1 = -4

同理-2 >> 1是一样的计算方法,这里就不演示了。

无符号右移

上面在进行左移和右移时,我有一点没讲到,就是在对补码进行移动时,符号位是固定不动的,而无符号右移是指在进行移动时,符号位也会跟着一起移动
比如-2 >>> 1

-2用原码表示为10000000 00000000 00000000 00000010
-2用反码表示为11111111 11111111 11111111 11111101
-2用补码表示为11111111 11111111 11111111 11111110

-2的补码右移1位为:01111111 11111111 11111111 11111111
右移后的补码对应的反码、原码为:01111111 11111111 11111111 11111111 (因为现在的符号位为0,表示正数,正数的原、反、补码都相同)
所以,对应的十进制为2147483647。
也就是-2 >>> 1 = 2147483647

总结

文章写的可能比较乱,希望大家能看懂,能有所收获。这里总结一下,我们可以发现:
2 << 1 = 4 = 2*2
2 << 2 = 8 = 2*2*2
2 << n = 2 *(2的n次方)
m << n = m *(2的n次方)
右移则相反,所以大家以后在源码中再看到位运算时,可以参考上面的公式。

如果想第一时间学习更多的精彩的内容,请关注微信公众号:1点25

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76107.html

相关文章

  • 你不知道的按运算

    摘要:相信大家都知道二进制数按位运算的规则来看一些简单的例子单纯的二进制位之间的这些运算相当简单,但对我们实际编程并没有直接帮助,因为编程过程中需要的经常是数字间的运算,比如。 先来看LeetCode上的Divide Two Integers题目要求: Divide two integers without using multiplication, division and mod ope...

    luoyibu 评论0 收藏0
  • C语言中数据的存储

    摘要:这里强调一下,之前讲过一个操作符按位取反操作符,区别一下他俩,按位取反操作符是针对二进制数每一位全部都取反,包括符号位。 目录 传统艺能?过渡区?正片开始?数据类...

    LeanCloud 评论0 收藏0
  • 数据的表示:原码反码补码、移码以及浮点数的运算

    摘要:而相应的,科学家们又提出了补码这一概念。因此在计算机中,为了避免运算错误,都是采用的补码进行加减法运算。原码反码补码移码数值表示范围在开始了解数值的表示范围之前,我们先来了解下什么叫做定点。 ...

    cnTomato 评论0 收藏0
  • Java运算符浅析

    摘要:在学习源码中,发现有大量使用位运算符,这样做的目的是为了节约内存开销和加快计算效率。位运算符,这个位代表这什么位二进制位简称位,是二进制记数系统中表示小于的整数的符号,一般用或表示,是具有相等概率的两种状态中的一种。 在学习源码中,发现有大量使用位运算符,这样做的目的是为了节约内存开销和加快计算效率。 位运算符,这个位代表这什么? 位:二进制位简称位,是二进制记数系统中表示小于2的整数...

    zhaofeihao 评论0 收藏0
  • <Principle Of Computer Organization>学习笔记——原码

    摘要:将补数的概念用到计算机中,便出现了补码这种机器数。通常,从原码形式入手来求补码。它与补码的区别是末位少加一个,因此很容易从补码的定义推出反码的定义。若真值为纯小数,它的反码形式为其中表示符号位。 一、原码表示法 原码表示法是一种最简单的机器数表示法,其最高位为符号位,符号位为0时表示该数为正,符号位为1时表示该数为负,数值部分与真值相同。若真值为纯小数,它的原码形式为Xs,.X1X2X...

    OnlyMyRailgun 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<