美文网首页Python笔记
Python基础之位运算符(含原码反码补码的通俗解释)

Python基础之位运算符(含原码反码补码的通俗解释)

作者: 化简可得 | 来源:发表于2020-02-12 17:47 被阅读0次

    目录

    1 二进制
    2 原码、反码、补码
    3 位运算符
    4 位运算符使用技巧

    上回学习运算符时,漏了位运算符,因为位运算符理解起来稍微有点复杂,所以要单独写一篇~

    要理解按位运算符,要先了解计算机进行存储和计算的底层逻辑。

    因此我们从最基础的二进制说起。

    1 二进制

    只要学过计算机,就不可能不知道二进制。

    我们知道,十进制是逢十进一,譬如11,左边的1在十位上,代表10,右边的1在个位上,就是1。

    把1502这个数字拆开看,就是有1个1000,5个100,0个10,2个1, 1502=1*10^3+5*10^2+0*10^1+2*10^0,也就是说,十进制中的位数对应的就是10的幂,个位是0次幂,十位是1次幂,百位是2次幂,以此类推……

    同理,二进制中的位数对应的就是2的幂,那么对于二进制下的1010,转化成十进制下的数,就是1*2^3+0*2^2+1*2^1+0*2^0=8+2=10

    用2进制数数,首先是0,然后是1,接下去是10,而不是2,因为二进制中只有0和1。

    小白可以练习一下从0写到10,写完对一下结果:

    image

    2 原码、反码、补码

    这三个码的产生,都和表示减法(负数)有关,他们的正数表示完全一样

    至于为什么为了表示个负数,出现了三个码,我们一个一个来说。

    2.1 原码

    日常生活中我们用负号(减号)解决了负数的表示问题,但在计算机中怎么加上这个负号呢?人们就想了个办法,用最高位存放符号,正数为0, 负数为1

    以4位二进值数为例,最高位是符号位,那么后面只有三位来表示数字。例如,0001表示1,要表示-1,就把最高位写成1,得到1001。

    当用8位来表示一个整数时,从右往左数的第8位即为符号位,当用16位来表示一个整数时,从右往左数的第16位即为符号位。我为了少写点数字>.<,本文举例都用4位。

    原码

    这种方法简单直观,但在减法运算中有问题。计算1减去1,就是0001和1001加起来,会得到1010,这是咋了?1加-1,等于-2?

    因此原码无法进行减法运算。

    2.2 反码

    正数的反码和正数的原码完全一样,对负数原码的非符号位取反,就得到负数的反码(其实也就是将正数的反码统统取反),譬如+1的反码是0001,-1的反码就是1110。

    反码

    此时我们计算+1和-1相加,即0001+1110=1111,正好是-0,相反数相加等于0,没有问题。

    但此时一个0有了两种表示法,0000和1111,一个数两种表示,有点奇怪。

    除此之外,虽然相反数相加没有问题,但是其他数的减法依旧不对劲,譬如0010+1110,等于10000,最高位1溢出,就是0000,所以2-1=0???

    所以,这个码还是不行。

    2.3 补码

    正数的补码和正数的原码完全一样,负数的补码等于负数的反码+1。但是,反码加1并不是补码的真正来历,只不过补码恰好等于反码加1,这么计算更加方便而已。

    补码

    关于补码的本质和定义,其实初看难以理解,但是仔细想想,会发现这就是自然而然、浑然天成的东西。

    我很喜欢一个词,“十方圆满”,一直以来我的微信签名都是这四个字。那么,这个词讲的是怎样一种状态呢?

    • 譬如,我们原地转个圈,就可以回到原点。
    • 譬如,我们在地球上,一直往东走可以到达的地方,一直往西走,也可以到达。
    • 譬如,我们把时钟的时针往前拨180度,和往后拨180度,得到的结果是一样的。
    • 再譬如,爱因斯坦说,如果人能看到无限远,那么他就能看到自己的后脑勺。

    我到底在说什么呢?

    对于四位二进制数,最大只能存放4位,就只有0000-1111这么大的空间,就只用2^4=16种排列组合的方式,空间是有限的。那么,这个空间圆不圆满呢?我们想办法让它从线性变成圆就好了,理解它,就像是理解24:00就是00:00,360°就是0°一样。

    先不管负数,假设我们有一条绳子,上面从左到右依次写着0、1、2…15、16,就像这样。


    image

    我们把绳子首尾相连,也就是把写着0和16的两端拧到一起,圈成一个圆。

    image

    这个圆上只有16个数字,16就等于0,这是为了方便后面和四位二进制数的16种排列组合相对应。

    我们从0出发,顺时针走1个单位,得到1,逆时针走1个单位,得到15,1+15=16。同样的,顺时针走7个单位得到7,逆时针走7个单位得到9,7+9=16。这个16有个专业的叫法,叫做

    这里的模是什么意思呢?简单举几个例子:

    • 24小时制下,24就是模。
    • 转一圈360°,360就是模。
    • 表上有12个刻度,12就是模。


      image

    现在看这个圆,从0开始顺时针转,数值越来越大,最后到15,再转一个单位,就又回到0,没有什么问题吧?

    好,我们开始引入负数,并且把顺时针看成加法,把逆时针看成减法,这下会得到什么呢?

    顺时针走1个单位,认为是加1,所以得到1,圆的右边还是1~7。逆时针走1个单位,就是减1,得到-1,同理,把圆的左边都填满,得到下图。

    image
    • 首先,距离0相同距离的数,相加等于0,这解决了相反数相加为0的问题;
    • 其次,这个圆可以把减法转化成加法,a-b=c,其实等同于a+(16-b)=c,因为模是16。可以自己验证下,譬如1-2=-1(逆时针走2个单位),在这里就等价与1+14=-1(顺时针走14个单位)。

    完美啊!接下来,我们把这个圆上的十进制数字,替换成二进制就好了。

    image

    问题来了,正数的二进制码毫无疑问,负数的二进制码要怎么推出来呢?还有,1对面那个问号应该是什么数字?在7和-7之间,应该是8呢,还是-8呢?

    先不管那个问号是什么数,7的二进制码是0111,再加1,得到1000,问号处的二进制码应该是1000,再加1,就是10001,以此类推,我们就能补满整个圆上的码。

    image

    补完你会发现,-1本就该是1111啊!因为1111再加1,为10000,但因为四位,最高位的1溢出了,所以就得到0000,-1加1可不就是0吗!

    相应的,我们可以验证-4加4,即1100+0100,等于10000,溢出位不算,0000啊!这种表示法下,所有的相反数相加都是0000~

    再看看别的减法呢,譬如6-3,即0110+1101,等于10011,即0011,就是3~哈哈,都通过验证了呢!

    现在就剩最后一个问题了,就是0的对面应该是什么?从7出发,加1应该是8,但是从-7出发,减1应该是-8,这个1000到底代表哪个数?

    其实不难发现,圆的右边都是正数,最高位皆为0,左边都是负数,最高位皆为1,这有点像原码中人为定义的最高位是符号位,所以1000自然而然应该是-8。

    虽然求补码的过程中没有特意留出一个符号位,但最终得到的补码却可以用最高位来判断正负。补码的符号位就是这么来的。

    image

    比比赖赖这么多,其实求补码没那么麻烦,可以汇总成一句话:正数补码不变,求负数补码用模减去其绝对值即可。

    前面我们说过模是16,那么求a-b,其实等同于a+(16-b),所以求-b这个负数的补码,用16减b不就行了吗?比如说,求-2,就用16的二进制码减去2的二进制码。可是,四位二进制码的空间里,根本没有16这个数啊?没有就对了,因为它是模,也就是10000,在八位中16的表示是00010000。

    那么,我们计算-2的补码,其实就转化成:
    10000
    -0010
    =1110

    2.4 小结

    总结一下:

    • 原码:将最高位作为符号位(0表示正,1表示负)。
    • 反码:如果是正数,则和原码一样;如果是负数,符号位为1,其余各位取反。
    • 补码:如果是正数,则和原码一样;如果是负数,将反码加上1。
    image

    很多文章在解释补码时,都是原码→反码→补码这样的思路。

    先介绍最简单的原码,它方便人读数,但无法做减法,接着引申出反码,它是原码过渡补码的中间产物,但无法解决0的问题,最后引出补码,反码直接加1即可得到补码,这个码可以完美解决前面两个码的问题。但实际上我们也知道了补码的发展过程并不如此,之所以提供这样的思路,只是为了便于计算补码。

    关于补码的定义和本质,我解释得挺业余的,举的例子也不够严谨,主要是为了方便自己理解和记忆。要看专业的解释,就得去找权威书籍或教材来看了。

    3 位运算符

    计算机底层在存储数据的时候,都是用补码存储,位运算符就是基于补码进行的计算,包括:

    1. 位逻辑运算符: 与&,或|,异或^,取反~。
    2. 位移运算符:左移<< ,右移>> 。
    位运算符 名称 算法
    & 按位与 两个二进制数相应位都为1,则该位的结果为1,否则为0
    l 按位或 两个二进制数相应位有一个为1时,结果位就为1
    ^ 按位异或 两个二进制数相应位不同时,结果为1
    ~ 按位取反 对二进制进行取反,即 1 取反为 0 ,0 取反为 1
    << 按位左移 将二进制数左移n位,相当于乘以2的n次方
    >> 按位右移 将二进制数右移n位,相当于除以2的n次方,如果不能整除,则向下取整
    a = 2
    b = 3
    print("a和b转换为二进制为:", bin(a), bin(b))
    -------------------------------------
    [output]: a和b转换为二进制为: 0b10 0b11
    

    下面我就用2和3,也就是0010和0011举例。

    a = 0010  #2
    b = 0011  #3
     
    a&b = 0010  #2
    a|b = 0011  #3
    a^b = 0001  #1
    ~a = 1101  #-3
    
    a<<1 = 0100 #左移一位,相当于乘2,得到4
    a>>1 = 0001 #右移一位,相当于除以2,得到1
    a>>2 = 0000 #右移两位,相当于除以4,不能整除时向下取整,得到0
    

    4 位运算符使用技巧

    在日常工作中,用到位运算符的场景似乎不多,它能用来做什么呢?

    4.1 按位与

    通常,我们写程序判断奇偶数,是除以2看余数。现在可以用该数和1进行按位与,结果是1,就是奇数,是0,则为偶数。

    def Odd_Even(x):
        if x&1 == 1:
            print(x,'是奇数')
        else:
            print(x,'是偶数')
    
    Odd_Even(666)
    ---------------------
    [output]: '666 是偶数'
    

    4.2 按位或

    任意数和1按位或,可以向上求最接近的奇数。

    6|1
    ---------------------
    [output]: 7
    
    7|1
    ---------------------
    [output]: 7
    

    4.3 按位异或

    一个数a,另一个数b进行两次异或运算,最后结果不变,即(a ^ b) ^ b = a。

    5^7
    ---------------------
    [output]: 2
    
    5^7^7
    ---------------------
    [output]: 5
    

    因此用异或运算调换两个数字的值。

    a = 5
    b = 7
    a = a^b
    b = b^a
    a = a^b
    print(a,b)
    ---------------------
    [output]: 7 5
    

    当然Python中其实可以用一行代码就完成交换。

    a = 5
    b = 7
    a,b = b,a
    print(a,b)
    ---------------------
    [output]: 7 5
    

    简单的加密也可以用异或运算,比如实际密码是password,既怕忘了又怕直接写下来被别人看到,就可以用一个简单的key作为密钥,两者作异或运算,得到tip,把这个tip记到小本子里。忘记密码时,将key和tip做异或运算,就能得到原密码啦~

    password = 587645
    key = 111111
    tip = password ^ key
    print(tip)
    ---------------------
    [output]: 607610
    
    tip ^ key
    ---------------------
    [output]: 587645
    

    4.4 按位取反

    对一个数按位取反,等于它的相反数减1。

    ~55
    ---------------------
    [output]: -56
    

    对一个数两次取反,结果不变。

    ~~55
    ---------------------
    [output]: 55
    

    4.5 按位左移

    a左移b位,就是把a转为二进制后左移b位,后面缺位补0,相当于a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2。

    5<<2
    ---------------------
    [output]: 20
    

    4.6 按位右移

    a右移b位,就是把a转为二进制后右移b位,前面缺位补0,相当于a除以2的b次方,并向下取整。

    14>>2
    ---------------------
    [output]: 3
    

    计算机中的数是用二进制来表示的,因此位运算可以更直接、更高效地实现运算操作。对于乘2除2,二进制左右位移一下就搞定,速度非常快,所以尽量用位移来代替代码中的乘除。

    最后注意一点,在Python中只能对整数进行位运算~

    文中图片的水印网址为本人CSDN博客地址:BeSimple

    参考链接:
    1)原码、反码、补码的产生、应用以及优缺点有哪些? - 张天行的回答 - 知乎
    2)原码,反码,补码的深入理解与原理
    3)Python位运算用途以及用法
    4)js 中位运算的应用
    5)位运算简介及实用技巧(一):基础篇

    相关文章

      网友评论

        本文标题:Python基础之位运算符(含原码反码补码的通俗解释)

        本文链接:https://www.haomeiwen.com/subject/wuucfhtx.html