前言

在学习框架源码底层时,有非常多的二进制运算,由于大学学习计算机基础时开小差,没有学习牢固,所以在看底层源码的算法逻辑时遇到二进制运算比较吃力,遂通过一篇博文来总结下二进制运算,记录一下。

读者认真阅读完整片文章,看完如果还不懂可以来砍我~

正文

1. 二进制基础

因为计算机底层是通过二进制来进行计算的,所以在计算机底层会将十进制转换为二进制。十进制就是逢10进1,二进制就是逢2进1。

就十进制来说,比如一百可以分为三位,个位、十位、百位, 用位数在下列表示 101这个十进制数。

百位 十位 十位
1 0 1

因此对于十进制来说,越往上进制位越大,比如千位、万位。

同理,对于二进制来说也有对应位数, 如果表示二进制的4。

4(2^2) 2(2^1) 1(2^0)
1 0 0

1 * 4 + 2 * 0 + 1 * 0 = 4

如果要表示二进制的5,则如下图

4(2^2) 2(2^1) 1(2^0)
1 0 1

4 * 1 + 2 * 0 + 1 * 1 = 5

如果要表示二进制的11,则如下图

8(2^3) 4(2^2) 2(2^1) 1(2^0)
1 0 1 1

8 * 1 + 4 * 0 + 2 * 1 + 1 * 1 = 11

在计算机中,1字节有8位二进制位。

2. 二进制运算

在计算机系统中,二进制运算包括了二进制逻辑运算和二进制算术运算,而逻辑运算和算术运算的主要区别在于,逻辑运算是按位进行,不像算术运算中位与位之间有进位和借位的联系。下面介绍的与、或、异运算就属于二进制的逻辑运算。

2.1 二进制逻辑运算

对于二进制的逻辑运算,记住一个口诀:

  1. 与(&)运算

    运算规则:
    0&0=0, 0&1=0, 1&0=0, 1&1=1

    二者为1则为1,否则都为0。

  2. 或(|)运算

    运算规则:
    0|0=0,0|1=1,1|0=1,1|1=1

    遇1则1,否则为0。

  3. 异(^)或运算

    运算规则:
    0^0=0, 1^0=1, 0^1=1, 1^1=0

    同为0,异为1。

由于算术运算中会设计符号数的运算,所以先介绍二进制中的源码、补码以及反码。

2.2 二进制算术运算

在二进制算术运算中,包括加、减、乘、除。

加法

0 + 0 = 1
1 + 1 = 10
1 + 0 = 1
0 + 1 = 1

乘法

0 × 0 = 0
1 × 0 = 0
0 × 1 = 0
1 × 1 = 1

减法

0-0=0
1-0=1
1-1=0
0-1=1

除法

0÷1=0
1÷1=1

3. 二进制的源码、补码以及反码

二进制源码是什么?

由于数字有正负之分,所以在计算机中通过在一个数的二进制的最高位存放符号(0为正,1为负),而其他数值位存放着就是数值的二进制位, 这就是二进制源码。需要知道的是,正数的源码、补码和反码都是一样的。

源码有缺点,就是不能直接进行运算,因为运算会出错。源码是有符号数的最简单的编码方式,便于输入输出,但作为代码加减运算时较为复杂。

二进制反码是什么?

反码通常是用来由源码求补码或者由补码求源码的过度码,根据定义可以根据补码的整数和小数中"0"的表示形式各有2中,+0和-0不一样。以8位机器数为例,
整数的"+0"源码为:0,0000000,反码为:0,0000000。整数的"-0"源码为:1,0000000,反码为:1,1111111。

反码跟源码是正数时一样,为负数是,除符号位外,其他为所有数值取反。

一句话概括就是,反码是用于计算负数补码的过度码。

二进制补码是什么?

由于数字有正负之分,所以在计算机中通过在一个数的二进制的最高位存放符号(0为正,1为负),而这就是机器数的补码。还需要知道的一点是,在计算机中是负数是以补码的形式存储的,由于正数的源码和补码相同,所以正数以源码或者补码的形式存储在计算机中都是正确的说法,而负数的源码和补码则不相同。二进制运算后,会将补码结果转换为源码之后,再计算其十进制值。

总结起来补码的作用就是:

  1. 使符号位能与有效值部分一起参加运算,从而简化运算规则.
  2. 使减法运算转换为加法运算,进一步简化计算机中运算器的线路设计 所有这些转换都是在计算机的最底层进行的,而在我们使用的汇编、C等其他高级语言中使用的都是原码。

源码和反码之间怎么转换?

源码除了最高位的符号位外,其他位数全部取反,得到的就是反码。

补码和反码之间怎么转换?

补码则是在反码的基础上加一。

小结

对于源码、补码和反码,三者均有符号位和数值位两部分,最高位为符号位,其余位均为数值位。符号位用0表示正,1表示负,而数值位三者表示方法都不同。在计算机系统中,数值一律用补码来
表示和存储,原因在于补码可以将符号位和数值域统一处理,同时加法和减法也可以同一处理,此外源码和补码互相转换,其运算过程是相同的,不需要额外的硬件电路。
一个负整数和其补数相加和为模,对一个整数的补码再求补码等于该整数自身,补码的正零与负零表示方法相同。

4. 二进制的移位运算符

在二进制中,移位运算符是一种位操作运算符。移位运算符可以在二进制的基础上对数字进行平移。按照平移的方向和位数填充规则可以将移位运算符分为三种:<<(左移)、>>(有符号右移)和>>>(无符号右移)。

4.1 <<(左移)

<<表示的是左移,将运算数的二进制整体左移指定的位数,低位用0补齐,因此左移不存在有符号和无符号的区别。

例如在java中int类型的十进制数16,由于在java中,int类型占4字节,1字节有8位, 所以其二进制源码表示为:

0000 0000 0000 0000 0000 0000 0001 0000

16 << 2 ,就是相当于将16的二进制数整体左移2为,然后低位补0,移位操作后:

0000 0000 0000 0000 0000 0000 0100 0000

那么我们再来看下对于负数的左移运算。

-16,其二进制源码为:
1000 0000 0000 0000 0000 0000 0001 0000

反码:
1111 1111 1111 1111 1111 1111 1110 1111

补码:
1111 1111 1111 1111 1111 1111 1111 0000

-16 << 2 , 对补码进行向左移2位, 低位补0
1111 1111 1111 1111 1111 1111 1100 0000
由于移位后最高位仍然为1,表示负数,所以需要借助反码来运算

反码为补码 - 1,则结果为:
1111 1111 1111 1111 1111 1111 1011 1111

源码为反码取反,则结果为:
1000 0000 0000 0000 0000 0000 0100 0000
-1 * (2 * 2 ^ 6) = -64

小结:对于左移运算,a << b,相当于 a * 2 ^ b。

4.2 >> (有符号右移)

在计算机中>> 表示有符号右移,就是将二进制整体右移指定位数,如果是正数,则高位用0补齐,如果是负数,则高位用1补齐。

仍然用java中16和-16来进行有符号右移操作,

16 >> 2

通过二进制补码是运算,最终结果为4,同样的-16 >> 2 结果为-4,详细的通过二进制运算结果这里就不展示了,参考左移中列出的详细步骤。

小结:对于有符号右移来说,如果是正数,移动指定位数后,高位用0来补齐;如果是负数,高位用1补齐。

4.3 >>>(无符号右移)

在计算机中通过>>>来表示无符号右移,不管是正数还是负数,高位都用0来补齐。

对于正数,无符号右移>>>和有符号右移>>结果都相同。

同样的用java中16和-16来进行无符号右移运算。

16 >>> 2 结果为 4。

而对于负数来说,无符号右移结果就需要进行计算了。

-16的补码为:
1111 1111 1111 1111 1111 1111 1111 0000

则无符号右移,结果为:
0011 1111 1111 1111 1111 1111 1111 1100

由于正数的源码、补码和反码都一样,所以移位后的源码为:
0011 1111 1111 1111 1111 1111 1111 1100,计算二进制得。

1 * 2 ^ 29 + 1 * 2 ^ 28 + 1 * 2 ^ 27 + 1 * 2 ^ 26 + … + 1 * 2 ^ 2 = 1073741820。

tip 可以通过小技巧来快速计算结果

聪明的读者估计能发现,在二进制位数中,高位的十进制值为其余右边进制之和 + 1,读者可以自行去验证。
所以,

0011 1111 1111 1111 1111 1111 1111 1100
^
取该位置计算得出的十进制值,然后-1,再减去 1 * 2^1 + 1 * 2 ^ 0,即:
2 ^ 30 - 1 - 2 ^ 1 - 2 ^ 0 =1073741824 - 1 - 2 - 1 = 1073741820。

5. 示例

比如变量 a & (-a) 用二进制怎么运算:

10 & (-10)

&是按位与,首先在计算机中数字都是以补码的形式存在的,比如:

int a = 10

+10,它的源码为:

0000 0000 0000 0000 0000 0000 0000 1010

由于正数的源码和补码相同,所以它的补码为:

0000 0000 0000 0000 0000 0000 0000 1010

对于-10,

它的源码为:

0000 0000 0000 0000 0000 0000 1000 1010

它的反码是源码的数值域取反,即:

1111 1111 1111 1111 1111 1111 1111 0101

它的补码是反码+1,即:

1111 1111 1111 1111 1111 1111 1111 0110

由于二进制运算都是用补码来计算的,所以 a & (-a) ,就是:

0000 0000 0000 0000 0000 0000 0000 1010 & 1111 1111 1111 1111 1111 1111 1111 0110 = 0000 0000 0000 0000 0000 0000 0000 0010

对于二进制来说,由于最高位为0,所以可以知道该数为正数,所以源码、补码和反码都一样。
而由于除开最高位符号位外的所有高位都为0,所以可以只看低四位,即0010,换算十进制后为2。

所以如果a=10,则 10 & (-10) = 2。

算完了&,那么再来计算下 ^ 和 | ,看下计算机中是如何通过补码来计算的。

10 ^ (-10)

由于10的补码为:
0000 0000 0000 0000 0000 0000 0000 1010
-10的补码为:
1111 1111 1111 1111 1111 1111 1111 0110

则10 ^ -10 的补码结果为:
1111 1111 1111 1111 1111 1111 1111 1100

由于最高位为1,则该数为负数,需要借助反码来计算其源码。

反码为补码 - 1,则结果为:
1111 1111 1111 1111 1111 1111 1111 1011

源码为反码取反,结果为:
1000 0000 0000 0000 0000 0000 0000 0100
则十进制结果为-4。

10 | (-10)

由于10的补码为:
0000 0000 0000 0000 0000 0000 0000 1010
-10的补码为:
1111 1111 1111 1111 1111 1111 1111 0110

由于最高位为1,则该数为负数,需要借助反码来计算其源码。

则10 | -10 的补码结果为:
1111 1111 1111 1111 1111 1111 1111 1110
反码结果为补码 - 1,则结果为:
1111 1111 1111 1111 1111 1111 1111 1101
源码为反码结果取反,结果为:
1000 0000 0000 0000 0000 0000 0000 0010

计算的十进制结果为 -2。

通过一个简单的示例分析,相信读者已经弄清楚了源码、补码和反码之间的关系以及在二进制运算中是如何运用的了。

在开源框架底层中算法会用到大量的二进制运算, 例如:在最近学习的Netty底层源码中,DefaultEventExecutorChooserFactory的底层源码有一个方法, 就是通过 a & (-a)来运算的。

@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {if (isPowerOfTwo(executors.length)) {return new PowerOfTowEventExecutorChooser(executors);} else {return new GenericEventExecutorChooser(executors);}
}
/** 用于计算val是否是2的幂,例如2、4、8、16*/
private static boolean isPowerOfTwo(int val) {return (val & -val) == val;
}private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {private final AtomicInteger idx = new AtomicInteger();private final EventExecutor[] executors;PowerOfTowEventExecutorChooser(EventExecutor[] executors) {this.executors = executors;}/** 通过二进制与运算计算出下标索引值,原理如下:* 假设:idx = 2 , 二进制表示为:0000 0010* executors.length = 16, 则executors.length - 1 = 15, 二进制表示为:0000 1111* * 由于是与运算,且executors.length高四位为0000,则只需要注意低四位的运算。* 则: 0010 & 1111 = 0010 ,十进制表示为2,* * 所以如果idx = 3, 则二进制为:0011 & 1111 = 0011 也为3,* 等idx = 16时,二进制为:0001 0000 & 1111 = 0000 0000 ,即十进制的1,* 这样就实现了在0 ~ 15 范围内循环获取下标索引的目的。*/@Overridepublic EventExecutor next() {return executors[idx.getAndIncrement() & executors.length - 1];}
}private static final class GenericEventExecutorChooser implements EventExecutorChooser {private final AtomicInteger idx = new AtomicInteger();private final EventExecutor[] executors;GenericEventExecutorChooser(EventExecutor[] executors) {this.executors = executors;}/** 通过取余运算,计算出下标索引*/@Overridepublic EventExecutor next() {return executors[Math.abs(idx.getAndIncrement() % executors.length)];}
}

总结

在计算机中,二进制的运算是比较重要的,可以看到在Java的许多开源框架底层就运用到了大量的二进制与或非运算,所以学好二进制的基础概念是非常重要的。

二进制运算以及源码、补码、反码概念讲解相关推荐

  1. 源码 补码 反码(转)

    大家都知道数据在计算机中都是按字节来储存了,1个字节等于8位(1Byte=8bit),而计算机只能识别0和1这两个数,所以根据排列,1个字节能代表256种不同的信息,即28(0和1两种可能,8位排列) ...

  2. 关于源码,反码,补码(正数--负数)---------(-128)自己的理解

    以一个字节为例 1.无符号位,一个字节可以存放0~255共256个数字:有符号位存放-128~127共256个数字: 2.无符号全都表示为正数:有符号位则首位表示正负数,正数首位为0,负数首位为1(因 ...

  3. 【无标题】源码、反码、补码

    一 . 源码.反码.补码 源码:整形的源码是4个字节,即32个比特位 源码的第一个数是符号位,0表示为正数,1是为负数 如:a = 10 0000000000000000000000000000101 ...

  4. c语言原码 补码 反码,C语言 原码--反码--补码

    //原码,反码,补码 #include #include //数值的表示方法--原码.反码和补码 //原码:最高位为符号位,其余各位为数值本身的绝对值 //反码: //正数:反码与原码相同 //负数: ...

  5. 计算机组成原理:原码,补码,反码,移码

    无符号数 寄存器的位数反映和决定无符号数的表示范围 比如八位的寄存器就只能储存十进制的0-255 有符号数 机器数与真值 在计算机当中没有硬件表示小数点的位置,而小数点的位置是我们自己约定的, 小数的 ...

  6. 计算机组成原理之 原码 补码 反码

    原码 补码 反码 前言 一.有符号数 1.1原码 补码 反码 总结 前言 在计算机中一般使用二进制表示各个有理数.在计算机中参与运算的数有两大类:有符号数和无符号数.无符号数就是没有符号的数,在寄存器 ...

  7. java反码补码原码作用_java原码补码反码关系解析

    本文为大家解析了java原码补码反码的关系,供大家参考,具体内容如下 原码:不管源数据是十进制还是十六进制,统统将数字转成二进制形式 反码:把原码的二进制统统反过来,0变成1,1变成0 补码:负数的反 ...

  8. Android园区部队人脸识别源码门禁项目讲解

    Android园区部队人脸识别源码门禁项目讲解 这边搞人脸识别相关项目有一段时间,今天抽时间讲述一个经典的人脸识别项目:部队人脸识别门禁系统. 大家都知道部队对人员管理安全要求是相当高的,很多保密的技 ...

  9. v62.02 鸿蒙内核源码分析(文件概念) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码

    司马牛忧曰:"人皆有兄弟,我独亡."子夏曰:"商闻之矣:死生有命,富贵在天.君子敬而无失,与人恭而有礼.四海之内,皆兄弟也.君子何患乎无兄弟也?" <论语 ...

最新文章

  1. 28个MongoDB经典面试题
  2. android 文件公有存储,如何将文件写入Android中的外部公共存储,以便从Windows中看到它们?...
  3. 缓冲运动之框架開始一级简单框架实例
  4. 学习了MPLS ×××
  5. Greenplum使用简明手册
  6. Python面试题总结(4)--数据类型(列表)
  7. 【AtCoder010】A - Addition(奇偶)
  8. womic网络错误_wo mic 电脑版下载-WO Mic Client下载 3.4 最新电脑版 - 河东下载站
  9. haosou属于搜索引擎的_中国的搜索引擎有哪些?
  10. c++ primer plus 第十四章 C++中的代码重用
  11. 栈:后进先出的线性表
  12. python实现虚拟键盘
  13. 前端每日实战:77# 视频演示如何用纯 CSS 创作旗帜飘扬的动画
  14. 未认证公众号接入公众号支付
  15. 安装AUTOROM、导入ROMs
  16. 百度地图离线开发demo(热力图)
  17. 机械硬盘计算机管理,机械硬盘怎么分区
  18. XML和JSON-自我小结
  19. 亲身经历,全盘加密需慎重!!开启TPM后可能会加密硬盘 这个按钮不要碰
  20. 从零开始学习SFR-- 2.0

热门文章

  1. 中央处理器 —— CPU的功能和基本结构
  2. Excel批量给字符加上双引号
  3. 深入理解计算机系统(第三版)
  4. 阿里云上Hadoop的安装
  5. 买汽车票的有什么手机软件?常乘汽车的朋友必备
  6. 003-机器学习背后的思维-针对入门小白的概念算法及工具的朴素思考
  7. 如何监测舆情?三款网上舆情搜索软件工具参考
  8. 360wifi linux驱动安装失败,安装360WIFI时,出现错误求解释
  9. 5.18 使用网格工具组合成梦幻色彩 [Illustrator CC教程]
  10. 服务器断电后自动重启