[Go版]算法通关村第十一关青铜——理解位运算的规则

目录

  • 数字在计算机中的表示:机器数、真值
  • 对机器数进一步细化:原码、反码、补码
  • 为何会有原码、反码和补码
  • 为何计算机中的按位运算使用的是补码?
  • 位运算规则
    • 与、或、异或和取反
    • 移位运算
    • 移位运算与乘除法的关系
    • 位运算常用技巧⭐️
  • 操作某个位的数据⭐️
    • 获取
    • 设置
    • 清零
    • 更新

数字在计算机中的表示:机器数、真值

  • 机器数
    一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器树时带符号的,在计算机中用一个数的最高位存放符号,正数为0,负数为1.
    比如:

    计算机字节长为8位,下面二进制就是机器数:
    十进制的 +3,转换成二进制就是 00000011
    十进制的 -3,转换成二进制就是 10000011
    
  • 真值
    因为机器数第一位是符号位,所以机器数的形式值就不等于真正的数值。
    所以为了好区别,将带符号位的机器数对应的真正数值称为机器数的真值。
    比如:

    0000 0001 的真值 = +000 0001 = +1
    1000 0001 的真值 = -000 0001 = -1
    

对机器数进一步细化:原码、反码、补码

  • 原码
    就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。比如如果是8位二进制:

    [+1] 原码 = 0000 0001
    [-1] 原码 = 1000 0001
    

    第一位是符号位,因为第一位是符号位,所以8位二进制的取值范围是:
    [1111 1111, 0111 1111],即[-127, 127]

  • 反码
    正数的反码是其本身,而负数的反码是在其原码的基础上,符号位不变,其余各个位取反。比如:

    [+1] = [0000 0001]= [0000 0001][-1] = [1000 0001]= [1111 1110]

    可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值,通常要将其转换成原码再计算。

  • 补码
    在应用中,因为补码能保持加减运算的统一,因此应用更广,其表示方法是:

    • 正数的补码就是其本身;
    • 负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1(即:在反码的基础上+1)
    [+1] = [0000 0001]= [0000 0001]= [0000 0001][-1] = [1000 0001]= [1111 1110]= [1111 1111]

    对于负数,补码表示方式也是人脑无法直观看出其数值的,通常也需要转换成原码再计算其数值。

为何会有原码、反码和补码

看个例子,计算十进制的表达式: 1-1=0,首先看原码的表示:

1 - 1 = 1 + (-1) = [00000001]+ [10000001]= [10000010]= -2

如果用原码表示,让符号位也参与计算,显然对于减法来说,结果是不正确的,这也是为何计算机内部不使用原码表示一个数。

为了解决原码做减法的问题就出现了反码,此时计算十进制的表达式为: 1-1=0

1 - 1 = 1 + (-1)
= [0000 0001]+ [1000 0001]= [0000 0001]+ [1111 1110]= [1111 1111]= [1000 0000]= -0

可以看到用反码计算减法结果的真值部分是正确的,但是"0"的表示有点奇怪,+0和-0是一样的,而且0带符号是没有任何意义,而且要浪费[0000 0000]原和[1000 0000]原两个编码来表示0。于是补码的出现,解决了0的符号以及两个编码的问题:

1-1 = 1 + (-1)
= [0000 0001]+ [1000 0001]= [0000 0001]+ [1111 1111]= [0000 0000]= [0000 0000]

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了,而且可以用[1000 0000]表示-128:

(-1) + (-127)
= [1000 0001]+ [1111 1111]= [1111 1111]+ [1000 0001]= [1000 0000]

-1-127的结果应该是-128,我们正好可以用[1000 0000]来表示-128,这样使用补码表示的范围为[-128, 127],这一点也比原码的[-127,127]好。

拓展一下,对于编程中常用到的32位int类型,可以表示范围是: [-2^31, 2^31-1] ,这也是我们在应用中经常见到的定义方式。

为何计算机中的按位运算使用的是补码?

对于计算机中的按位运算,包括与操作(AND)、或操作(OR)、异或操作(XOR)等,使用补码表示是最为常见的,因为补码可以统一处理正数和负数,而且在进行数值运算时能够保持一致性。

  • 统一性和一致性:补码表示法允许我们用同一种方式处理正数和负数。在补码中,负数的表示是通过正数的按位取反再加1来得到的。这就意味着无论是正数还是负数,在计算机内部都是用相同的机制进行表示和运算的,不需要针对正负数分别编写不同的逻辑。
  • 溢出处理:在进行二进制运算时,可能会出现溢出。使用补码可以更方便地处理溢出情况。例如,当两个正数相加得到负数的情况,或者两个负数相加得到正数的情况,在补码中能够更自然地处理,无需额外的特殊逻辑。
  • 零的表示:使用补码表示法,零的表示是唯一的,即所有位都是0,不管是正数还是负数的零。这样,进行与操作时,如果某一位是零,与任何数进行与操作都不会改变那一位的值。
  • 简化硬件逻辑:使用补码可以简化硬件逻辑设计。在计算机内部,进行补码的加法、减法、与操作等都可以使用相同的电路结构,从而降低了硬件设计的复杂度。

综上所述,补码在计算机内部的数值表示和运算中具有很多优势,可以统一处理正数和负数、简化逻辑设计,并且处理溢出等情况更为方便。因此,在计算机中的位运算,如与操作,通常都是使用补码表示来进行的。

位运算规则

位运算主要有:与、或、异或、取反、左移和右移。其中左移和右移统称为移位运算,移位运算又分为算数移位和逻辑移位。

与、或、异或和取反

  • 与运算符:& ,运算规则是:对于每个二进制位,两个数对应的位都是1时,结果为1,否则结果为0。(都是1为1,否则是0)

    0 & 0 = 0
    0 & 1 = 0
    1 & 0 = 0
    1 & 1 = 1
    
  • 或运算符:| ,运算规则是:对于每个二进制位,两个数对应的为都是0时,结果为0,否则结果为1。(都是0为0,为否是1)

    0 | 0 = 0
    0 | 1 = 1
    1 | 0 = 1
    1 | 1 = 1
    
  • 异或运算符:⊕(在代码中用∧ 表示异或),运算规则是:对于每个二进制位,两个数对应的位相同时,结果为 0,否则结果为 1。(相同为0,不同为1)

    0 ⊕ 0 = 0
    0 ⊕ 1 = 1
    1 ⊕ 0 = 1
    1 ⊕ 1 = 0
    
  • 取反运算符:~ ,运算规则是:对一个数的每个二进制位进行取反操作,0变成1,1变成0。(每个位 0变1,1变0)

    ~0 = 1
    ~1 = 0
    

移位运算

  • 左移运算符: <<,将全部二进制位向左移动若干位,高位丢弃,低位补 0。对于左移运算,算术移位和逻辑移位是相同的

  • 右移运算符: >>,将全部二进制位向右移动若干位,低位丢弃,高位的补位由算术移位或逻辑移位决定:

    • 算术右移时,高位补最高位;
    • 逻辑右移是,高位补0。
原始     0000 0110	6
右移一次:0000 0011	3 相当于除以2
左移一次:0000 1100	12 相当于乘以2

移位运算与乘除法的关系

观察上面的例子可以看到,移位运算可以实现乘除操作。由于计算机的底层的一切运算都是基于位运算实现的,因此使用移位运算实现乘除法的效率显著高于直接乘除法的。

左移运算对应乘法运算。将一个数左移 k位,等价于将这个数乘以 2^k。
例如,29 左移 2 位的结果是 116,等价于 29×4。当乘数不是 2 的整数次幂时,可以将乘数拆成若干项 2 的整数次幂之和,例如,a×6 等价于 (a<<2)+(a<<1)。对于任意整数,乘法运算都可以用左移运算实现,但是需要注意溢出的情况,例如在 8 位二进制表示下,29 左移 3 位就会出现溢出。

算术右移运算对应除法运算。将一个数右移 k 位,相当于将这个数除以 2^k。
例如,50 右移 2 位的结果是 12,等价于 50/4,结果向下取整。

从程序实现的角度,考虑程序中的整数除法,是否可以说,将一个数(算术)右移 k 位,和将这个数除以 2^k等价?
对于 0和正数,上述说法是成立的,整数除法是向 0 取整,右移运算是向下取整,也是向 0 取整。
但是对于负数,上述说法就不成立了,整数除法是向 0 取整,右移运算是向下取整,两者就不相同了。例如,(−50)>>2 的结果是 −13,而 (−50)/4 的结果是 −12,两者是不相等的。
因此,将一个数(算术)右移 k 位,和将这个数除以 2^k是不等价的。
算法出题这早就考虑到了这一点,因此在大部分算法题都将测试数据限制在正数和0的情况,因此可以放心的左移或者右移。

位运算常用技巧⭐️

位运算的性质有很多,此处列举一些常见性质,假设以下出现的变量都是有符号整数。

  • 幂等律:a &a=a,a ∣ a=a(注意异或不满足幂等律);

  • 交换律:a & b=b & a,a ∣ b=b ∣ a,a⊕b=b⊕a;

  • 结合律:(a & b) & c=a & (b & c),(a ∣ b) ∣ c=a ∣ (b ∣ c),(a⊕b)⊕c=a⊕(b⊕c);

  • 分配律:(a & b) ∣ c=(a ∣ c) & (b ∣ c),(a ∣ b) & c=(a & c) ∣ (b & c),(a⊕b) & c=(a & c)⊕(b & c);

  • 德摩根律:∼(a & b)=(∼a) ∣ (∼b),∼(a ∣ b)=(∼a) & (∼b);

  • 取反运算性质:−1=∼0,−a=∼(a−1);

    −1=~0 
    在计算机中,负数的表示通常使用二进制的补码表示法。
    -1 = [1000 0001]原 = [1111 1110]反 = [1111 1111]补
    0 =  [0000 0000]原 = [0000 0000]反 = [0000 0000]补~0 = [1111 1111]补
    在这种特定情况下,-1与~0的二进制表示是相同的。
    
    −a=~(a−1)
    假设a的二进制表示是:00000101(即十进制的5)。考虑等式的右边:~(a - 1)
    那么(a - 1)的二进制表示就是00000100(即十进制的4)。
    对(a - 1)的每一位按位取反,得到:11111011。计算等式的左边:-a
    首先取a的每一位按位取反,得到:11111010。
    然后再加1,得到:11111011,与等式右边的结果相同。这个等式的成立是基于补码运算和按位取反的性质。
    
  • 与运算性质:a & 0=0,a & (−1)=a,a & (∼a)=0;

    a & (−1)=a
    在二进制补码表示中,-1 的所有位都是1,即所有位取反后加1。
    -1 = [1000 0001]原 = [1111 1110]反 = [1111 1111]补
    无论 a 的某一位是0还是1,在与 -1 进行与操作后,结果的对应位都会保持不变。
    
    a & (~a)=0
    对于任意整数 a,按位取反操作 ~a 将 a 的每一位取反(0 变 1,1 变 0)
    如果 a 的某一位是 0,那么 ~a 的对应位是 1,所以在 & 运算中结果位为 0。
    如果 a 的某一位是 1,那么 ~a 的对应位是 0,所以在 & 运算中结果位也为 0。
    
  • 或运算性质:a ∣ 0=a,a ∣ (∼a)=−1;

    a ∣ (~a)=−1
    对于任意整数 a,按位取反操作 ~a 将 a 的每一位取反(0 变 1,1 变 0)
    如果 a 的某一位是 0,那么 ~a 的对应位是 1,所以在 | 运算中结果位为 1。
    如果 a 的某一位是 1,那么 ~a 的对应位是 0,所以在 | 运算中结果位也为 1。
    
  • 异或运算性质:a⊕0=a,a⊕a=0;

处理位操作时,还有很多技巧,不要死记硬背,理解其原理对解决相关问题有很大帮助。下面的示例中,1s和0s分别表示与x等长的一串1和一串0:
在这里插入图片描述

操作某个位的数据⭐️

如何获取、设置和更新某个位的数据,也有固定的套路。例如:

获取

该方法是将1左移 i 位,得到形如00010000的值。接着对这个值与num执行”位与“操作,从而将 i 位之外的所有位清零,最后检查该结果是否为零。不为零说明 i 位为1,否则 i 位为0。代码如下:

func getBit(num int, i int) bool {return num & (1 << i) != 0
}

设置

setBit先将1左移 i 位,得到形如00010000的值,接着堆这个值和num执行”位或“操作,这样只会改变 i 位的数据。这样除 i 位外的位均为零,故不会影响num的其余位。代码如下:

func setBit(num int, i int) int {return num | (1 << i)
}

清零

该方法与setBit相反,首先将1左移 i 位获得形如00010000的值,对这个值取反进而得到类似11101111的值,接着对该值和num执行”位与“,故而不会影响到num的其余位,只会清零 i 位。

func clearBit(num int, i int) int {mark := ~(1 << i)return num & mark
}

更新

这个方法是将setBit和clearBit合二为一,首先用诸如11101111的值将num的第 i 位清零。接着将待写入值 v 左移 i 位,得到一个 i 位为 v 但其余位都为0的数。最后对之前的结果执行”位或“操作,v为1这num的 i 位更新为1,否则为0:

func updateBit(num int, i int, v int) int {mark := ~(1 << i)return (num & mark) | (v << i)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/40124.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity用NPOI创建Exect表,保存数据,和修改删除数据。以及打包后的坑——无法打开新创建的Exect表

先说坑花了一下午才找到解决方法解决&#xff0c; 在Unity编辑模式下点击物体创建对应的表&#xff0c;获取物体名字与在InputText填写的注释数据。然后保存。创建Exect表可以打开&#xff0c;打包PC后&#xff0c;点击物体创建的表&#xff0c;打不开文件破损 解决方法&#…

大数据培训前景怎么样?企业需求量大吗

大数据行业对大家来说并不陌生&#xff0c;大数据行业市场人才需求量大&#xff0c;越早入行越有优势&#xff0c;发展机会和上升空间等大。不少人通过大数据培训来提升自己的经验和自身技术能力&#xff0c;以此来获得更好的就业机会。 2023大数据培训就业前景怎么样呢?企业需…

从视觉装备到智能驾驶,天准科技能否打造第二增长极?

智能网联汽车已经成为了上市公司跨界布局的热门赛道。 天准科技是工业视觉智能装备领域的龙头企业&#xff0c;主要客户包括苹果、三星等企业。招股说明书显示&#xff0c;2016年至2018年&#xff0c;天准科技来源于苹果公司及其供应商的收入合计占比达到49.98%、67.99%及76.0…

JVS开源基础框架:平台基本信息介绍

JVS是面向软件开发团队可以快速实现应用的基础开发脚手架&#xff0c;主要定位于企业信息化通用底座&#xff0c;采用微服务分布式框架&#xff0c;提供丰富的基础功能&#xff0c;集成众多业务引擎&#xff0c;它灵活性强&#xff0c;界面化配置对开发者友好&#xff0c;底层容…

互联网账号被封禁解决办法,以qq为例

百度搜索&#xff1a;互联网信息服务投诉平台 电脑端浏览器&#xff1a;打开 ts.isc.org.cn 推荐使用360极速浏览器 谷歌浏览器 提交完成后&#xff0c;将投诉码保存&#xff0c;可以在“查询评价”处用投诉码查询进度

windows安装go,以及配置工作区,配置vscode开发环境

下载安装go 我安装在D:\go路径下配置环境变量 添加GOROOT value为D:\go修改path 添加%GOROOT%\bin添加GOPATH value为%USERPROFILE%\go 其中GOPATH 是我们自己开发的工作区&#xff0c;其中包含三个folder bin,pkg,以及src&#xff0c;其中src为我们编写代码的位置 配置vscod…

uniapp 使用 uni push 2.0 推送消息

因为之前使用uni push 1.0&#xff0c;开通账号和配置厂商就不写了。只说一点&#xff0c;配置厂商很重要&#xff0c;不然收不到离线推送的消息。那么就直接开始咯&#xff01;&#xff01;&#xff01; 一、创建并关联云服务空间 1.创建云服务空间&#xff0c;右键项目【创…

Java进阶(3)——手动实现ArrayList 源码的初步理解分析 数组插入数据和删除数据的问题

目录 引出手动实现ArrayList定义接口MyList<T>写ArrayList的实现类增加元素删除元素 写测试类进行测试数组插入数据? 总结 引出 1.ArrayList的结构分析&#xff0c;可迭代接口&#xff0c;是List的实现&#xff1b; 2.数组增加元素和删除元素的分析&#xff0c;何时扩容…

利用HTTP代理实现请求路由

嘿&#xff0c;大家好&#xff01;作为一名专业的爬虫程序员&#xff0c;我知道构建一个高效的分布式爬虫系统是一个相当复杂的任务。在这个过程中&#xff0c;实现请求的路由是非常关键的。今天&#xff0c;我将和大家分享一些关于如何利用HTTP代理实现请求路由的实用技巧&…

数据结构----哈夫曼树

这里写目录标题 基本概念引子基本概念各种路径长度各种带权路径长度结点的带权路径长度树的带权路径长度哈夫曼树 哈夫曼树的构造理论基础构造思想总结 哈夫曼树的实现哈夫曼编码前缀编码哈夫曼编码的思想案例代码实现 编码与解码 基本概念 引子 哈夫曼树就是寻找构造最优二叉…

Docker容器基础

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、Docker概述1、docker是什么2、Docker的设计宗旨3、容器在内核中支持2种重要技术&#xff1a; 三、Docker的核心概念四、Docker相关命令1.安装依赖包2.设置阿里云…

无线测温产品在半导体制造项目的应用

摘 要&#xff1a;半导体被誉为“制造业的大脑”&#xff0c;在关系国家安全和国民经济命脉的主要行业和关键领域占据支配地位&#xff0c;是国民经济的重要支柱。 随着数字技术的发展和数字经济在国民经济中所占比重越来越高&#xff0c;半导体产业的重要性还会进一步提升。安…

C++QT教程3——手册4.11.1自带教程(笔记)——创建一个QT快速应用

文章目录 创建一个QT快速应用创建项目创建主视图添加应用逻辑为视图添加动画素材文件 参考文章 创建一个QT快速应用 本教程使用内置的QML类型&#xff0c;介绍了Qt Quick的基本概念。有关可以选择的用户界面选项的更多信息&#xff0c;请参阅用户界面。 本教程描述了如何使用…

部署mysql到win10电脑上

中间出现了很多问题&#xff0c; 记录一下 我这边是去官网下载的 &#xff0c;链接&#xff1a;https://dev.mysql.com/downloads/mysql/ 我这边选了不是最新版本的MySQL&#xff0c;因为第一次安装8.1.0版本的&#xff0c;死活运行不起来&#xff0c;直接卸载安重装了&#x…

常用的分布式计算引擎

记录一下&#xff0c;作为备忘。 常用的分布式计算引擎 多表关联的问题&#xff0c;由于NoSQL数据库主要用于海量存储和单表查询&#xff0c;一般都不支持join&#xff0c;需借助更上层的计算框架来实现多表关联&#xff0c;比如: 计算框架支持数据源执行效率Hive本地文件、…

后端项目打包上传服务器记录

后端项目打包上传服务器记录 文章目录 后端项目打包上传服务器记录1、项目打包2、jar包上传服务器 本文记录打包一个后端项目&#xff0c;上传公司服务器的过程。 1、项目打包 通过IDEA的插件进行打包&#xff1a; 打成一个jar包&#xff0c;jar包的位置在控制台可以看到。 2、…

ssm蜀都天香酒楼网站设计与实现

ssm蜀都天香酒楼的网站设计与实现028 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘要 近年来&#xff0c;信息化管理行业的不断兴起&#xff0c;使得人们的日常生活越来越离不开计算机和互联网技术。首…

selenium语法进阶+常用API

目录 浏览器操作 浏览器回退&#xff0c;前进 与刷新 浏览器窗口设置大小 浏览器设置宽高 浏览器窗口最大化 浏览器控制滚动条 信息打印 打印页面的标题和当前页面的URL 定位一组元素 鼠标和键盘事件 键盘 鼠标 下拉框操作 通过索引定位&#xff08;se…

【BASH】回顾与知识点梳理(三十二)

【BASH】回顾与知识点梳理 三十二 三十二. SELinux 初探32.1 什么是 SELinux当初设计的目标&#xff1a;避免资源的误用传统的文件权限与账号关系&#xff1a;自主式访问控制, DAC以政策规则订定特定进程读取特定文件&#xff1a;委任式访问控制, MAC 32.2 SELinux 的运作模式安…

安科瑞变电所运维平台在电力系统中应用分析

摘要&#xff1a;现代居民生活、工作对电力资源的需求量相对较多&#xff0c;给我国的电力产业带来了良好的发展机遇与挑战。探索电力系统基本构成&#xff0c; 将变电运维安全管理以及相应的设备维护工作系统性开展&#xff0c;能够根据项目实践工作要求&#xff0c;将满足要求…