Data Structures - Primitive Types

Posted by R. on January 2, 2021

转载自: https://www.cnblogs.com/Neeo/articles/10536202.html

Python 进行补码运算

原码

原码(true form)是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。 口诀在此:一个正数,转换为二进制位就是这个正数的原码。负数的绝对值转换成二进制位然后在高位补1就是这个负数的原码。 比如说int类型的3的原码是11B(B表示二进制位,这里你可以多了解一些进制之间的转换),在32为机器上占4个字节,所以,高位补0就是: 00000000 00000000 00000000 00000011 # 一个字节8个bit位

那么int类型的-3的绝对值的二进制位就是11B展开后最高位补1就是: 10000000 00000000 00000000 00000011 但是呢,原码也有缺点,原码中的0分为+0和-0。不仅如此,在进行不同符号的加法运算或者同符号的减法运算时,不能直接判断出结果的正负,我们必须要将两个值的绝对值进行比较。然后再进行加减操作。最后符号由绝对值大的决定,于是乎,下面有请反码登场。

反码

反码是数值存储的一种,多应用于系统环境设置,如linux平台的目录和文件的默认权限的设置umask,就是使用反码原理。 口诀不能忘:正数的反码就是原码,负数的反码等于原码除符号位以外所有位取反。 比如还是刚才的那个int类型的3的反码是:

00000000 00000000 00000000 00000011 # 正数的反码就是原码,这么写没毛病 那int类型的-3的反码是,让我们默念公式:负数的反码等于原码除符号位以外所有位取反!

10000000 00000000 00000000 00000011	 # -3的原码
11111111 11111111 11111111 11111100	 # 最高位为符号位,不变,其余取反 

这样,反码解决了加减法运算问题(不理解就自己再查吧),我们就该着手处理+0和-0的问题了,有请补码上台领奖!

补码

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路支持。 记住口诀:正数的补码与原码相同,负数的补码为其原码除符号位外所有位取反(这就是反码了),然后最低位加1。 还是那个int类型的3的补码是:

00000000 00000000 00000000 00000011 # 正数的补码与原码一致 那么int类型的-3的补码就是,让我们手掐口诀:

10000000 00000000 00000000 00000011  # -3的原码
11111111 11111111 11111111 11111100	 # 负数的补码为其原码除符号位外所有位取反
11111111 11111111 11111111 11111101  # 然后最低位加1,完美

原、反、补码小结# 原、反、补码小结:

正数的反码和补码都与原码相同 负数的反码为该数的原码除符号位外所有位取反 负数的补码为该数的原码除符号位外所有位取反,然后最低位加1 优缺点:

原码最好理解,但是存在加减法运算不方便的问题,还有俩零蛋捣乱 反码稍微难点,但仅解决了加减法的问题。俩零继续捣乱 补码理解相对困难,但解决了上面的俩问题

Python中的按位运算# 我们在之前的原、反、补码中了解了基本的数值存储。那么这里就开始具体的学习Python中按位是怎么运算的,首先来看规则。Python中的按位运算规则如下表所示:

运算符 描述 & 按位运算符,参与运算的两个值,如果相应位都为1,则该位的结果为1,否则为0 ^ 按位异或运算符,当两个对应的二进位相异时,结果为1 ~ 按位取反运算符,对数据的每个二进制位取反,即把1变为0,把0变为1 | 按位或运算,只要对应两个二进制位有一个为1时,结果就为1 « 左移动运算符:运算数的各二进制位全部左移若干位,由« 右边的数字指定了移动的位数,高位丢弃,低位补0

右移动运算符:把>>左边的运算数的各二进制位全部右移若干位,>> 右边的数字指定了移动的位数 在Python的按位运算符中,只有反转~运算符是单目运算,其余都是双目运算。

按位与 &# 按位与的规则是:参与运算的两个值,如果相应位都为1,则该位的结果为1,否则为0,也就是说:

1 & 1 = 1 1 & 0 = 0 0 & 1 = 0 0 & 0 = 0 我们首先来看一个示例:

3 & 5 1 分析,我们首先来看它们各自的补码,我们接下来的演示只用一个字节8位表示就行,32位太长了(搞起来难受):

0000 0011 # 3的补码 0000 0101 # 5的补码 0000 0001 # 根据按位与的规则,得出补码结果 得出的结果是补码类型的, 我们要先把补码转换为原码,再将二进制转换为十进制的结果。正数的补码等于原码,所以结果就是1。 再来个示例:

-2 & -3 -4 老套路,先找各自的补码,再求结果:

1111 1110 # -2的补码 1111 1101 # -3的补码 1111 1100 # 结果 我们将补码转换为原码,默念口诀:补码转原码,符号位不变,数值为按位取反,末位加1:

1111 1100 # 补码 1000 0011 # 符号位不变,数值位按位取反 1000 0100 # 末位加1 想着最高位的符号位为负,二进制100对应的十进制是4,最终结果就是-4。 再来个例子:

-2 & 3 2 老套路,拿到它们各自的补码,再求结果:

1111 1110 # -2的补码 0000 0011 # 3的补码 0000 0010 # 结果 找到对应的十进制是2。 小结:在按位与的结果中,只有是负数的情况下,才需要将补码转换为原码,然后再求对应的十进制数。

按位或 |# 先把口诀放这里:按位或运算,只要对应两个二进制位有一个为1时,结果就为1。也就是说:

1 | 1 = 1 1 | 0 = 1 0 | 1 = 1 0 | 0 = 0 再把例子拿过来:

3 | 5 7 拿到补码:

0000 0011 # 3的补码 0000 0101 # 5的补码 0000 0111 # 结果 二进制的111转为十进制是7。 再来个例子:

-2 | -3 -1 各自的补码是:

1111 1110 # -2的补码 1111 1101 # -3的补码 1111 1111 # 结果 拿到了结果,我们还需要将补码转换为原码再转10进制:

1111 1111 # 结果 1000 0000 # 高位不变,其余取反 1000 0001 # 末位加1 最高位的是负号,最终的结果是-1。 再来个例子:

-2 | 3 -1 老套路,拿到它们各自的补码,再求结果:

1111 1110 # -2的补码 0000 0011 # 3的补码 1111 1111 # 结果 继续补码转原码再转十进制:

1111 1111 # 结果 1000 0000 # 高位不变,其余取反 1000 0001 # 末位加1 最高位为负号,找到对应的十进制是-1。

按位异或 ^# 先把规则列出来:按位异或运算符,当两个对应的二进位相异时,结果为1,也就是说:

1 ^ 1 = 0 1 ^ 0 = 1 0 ^ 1 = 1 0 ^ 0 = 0 再把例子拿过来:

3 ^ 5 6 拿到补码:

0000 0011 # 3的补码 0000 0101 # 5的补码 0000 0110 # 结果,注意按照规则来 正整数的结果一目了然,二进制的110转为十进制是6。 再来个例子:

-2 ^ -3 3 各自的补码是:

1111 1110 # -2的补码 1111 1101 # -3的补码 0000 0011 # 结果 首先来看011对应的十进制是3,所以最终结果是3。 再来个例子:

-2 ^ 3 -3 老套路,拿到它们各自的补码,再求结果:

1111 1110 # -2的补码 0000 0011 # 3的补码 1111 1101 # 结果 结果是负数,只能将补码转原码再转10进制了:

1111 1101 # 结果 1000 0010 # 高位不变,其余取反 1000 0101 # 末位加1 最高位为负号,二进制的101是3, 所以对应的十进制是-3。 最后,来总结一下异或特点,0异或任何数得这个数(0异或0得0),一个数与自己异或时结果为0:

0 ^ 0 0 0 ^ 3 3 0 ^ -3 -3 3 ^ 3 0 按位取反 ~# 首先来说,按位取反是单目运算。所以,别上来就:

2 ~ 3 File “", line 1 2 ~ 3 ^ SyntaxError: invalid syntax 显得可low了,一点都不!专!业!!!

书归正传,先把规则列出来:按位取反运算符,对数据的每个二进制位取反,即把1变为0,把0变为1。 来个例子:

~ 3 -4 拿到-3的补码:

0000 0011 # 3的补码 按每个二进制位取反:

1111 1100 # 结果是负数,还要转为原码 1000 0011 # 高位不变,其余取反 1000 0100 # 末位加一 别忘了最高位的负号,二进制的100转为十进制是-4。 再来个例子:

~ -2 1 -2的补码是:

1111 1110 # -2的补码 按位取反:

0000 0001 得到的结果一目了然,是1。

按位左移 «# 先把规则列出来:左移动运算符,运算数的各二进制位全部左移若干位,而 « 右边的数字指定了移动的位数,高位丢弃,低位补0 来个示例:

2 « 3 16 先拿到2的补码:

0000 0010 # 2的补码 整体(这里也就是1)开始往左移动,移动的位数是3位,所以得的移动结果:

0001 0000 最终的十进制结果是16。

按位右移 »# 先把规则列出来:右移动运算符,把»左边的运算数的各二进制位全部右移若干位,» 右边的数字指定了移动的位数 来个示例:

2 » 3 0 先拿到2的补码:

0000 0010 # 2的补码 从1(从右往左数,第二位)开始往右移动,移动的位数是3位,所以得的移动结果:

0000 0000
移动到第3位时,把1就移没了,剩下全是0最终的十进制结果是0。

位运算的应用# 异或用来做加密混淆,比如JavaScript为了防止源码被盗,除了美化、压缩就是可以做混淆。包括C中可以做加密算法。 按位与和按位或可以做矩阵、跑马灯。IOS中可以用来控制按钮的操作。 可以制定不同的规则,来通过一串二进制就可以表示不同的状态信息,比如一串32位的二级制位,就可以有表现32个状态信息。 随便举几个示例

示例1:来自leetcode中的一个题。题目是给定一个非空的数组,除了一个元素外,其余的元素都会出现两次,找到这个仅出现一次的元素。注意:你的算法应具有线性运行时的复杂性O(n),如果不能有额外的内存开销更好。我们的老套路是: def f1(): l1 = [1, 1, 2, 2, 3, 4, 4, 5, 5] s1 = ‘‘.join([str(i) for i in l1]) for i in s1: if s1.count(i) == 1: return i 我们可以用异或的特点(0异或任何数得这个数,一个数与自己异或时结果为0)来帮助我们解决这个问题:

def f2(): l1 = [1, 2, 1, 2, 3, 4, 3, 4, 5, 5, 6, 7, 6, 8, 8] temp = 0 for i in l1: temp ^= i print(i, temp) return temp 示例2,还是leetcode中的题。给定一个整数,求出该数的二进制数中有多少个1,这里我们可以使用字符串的count来解决该问题: print(‘00000000000000000000000000001011’.count(‘1’)) print(bin(10), bin(10).count(‘1’)) 除此之外,我们也可以使用按位右移和按位与也可以完成该需求,我们只需要将该数不断地右移,然后和1按位与,知道这个数为0即可:

def f1(n): temp = 0 for i in bin(n): # 10的二进制是1010 print(n & 1) temp += n & 1 n = n » 1 return temp print(f1(10)) # 2