按位操作一元~(反转)

89

我对~操作符有些困惑。代码如下:

a = 1
~a  #-2
b = 15
~b  #-16

~ 是如何工作的?

我以为,~a 会是这样的:

0001 = a
1110 = ~a 

为什么不呢?


3
例如:https://dev59.com/5nA75IYBdhLWcg3w7tx6。 - S.Lott
5个回答

76

您说得没错,这是由于采用了二进制补码整数表示法所致。

在16位中,1被表示为0000 0000 0000 0001。对其取反,得到的是1111 1111 1111 1110,它等于-2。同样地,15被表示为0000 0000 0000 1111。对其取反,得到的是1111 1111 1111 0000,它等于-16。

通常情况下,~n = -n - 1


9
如何在Python 3中获取位表示的字符串? - Janus Troelsen
1
在Python交互式shell中,尝试使用print(int('0001', 2))进行操作。参考:https://wiki.python.org/moin/BitManipulation - Praneeth
6
我相信这是Janus想要的反函数。 - sinekonata
1
int('00101010', 2) 的倒数是 f'{42:08b}'。例如,格式化字符串 08b 将导致二进制表示形式的42填充零,如果字符数少于8。 - Lukas

38

'~'操作符被定义为: "x的按位反转被定义为-(x+1)。它只适用于整数。"Python文档 - 5.5

这句话中最重要的部分是它与'整数'(也称为整型)有关。您的示例表示一个4位数。

'0001' = 1 

4位数字的整数范围为“-8..0..7”。另一方面,您可以使用“无符号整数”,不包括负数,您的4位数字的范围将是“0..15”。

由于Python操作整数,因此您描述的行为是可以预期的。整数使用二进制补码表示。 对于4位数字,它看起来像下面这样。

 7 = '0111'
 0 = '0000'
-1 = '1111'
-8 = '1000'

如果你的操作系统是32位的,Python使用32位整数表示。你可以通过以下方式检查最大整数:

sys.maxint # (2^31)-1 for my system

如果你希望得到一个无符号整数返回你的4位数字,你需要进行掩码处理。

'0001' = a   # unsigned '1' / integer '1'
'1110' = ~a  # unsigned '14' / integer -2

(~a & 0xF) # returns 14

如果您想获取无符号的8位数字范围(0..255),只需使用以下代码:

(~a & 0xFF) # returns 254

9
Python 2 和 3 中的整数都具有无限精度。 - Antti Haapala -- Слава Україні
@AnttiHaapala 在你发表评论的时候,你没想到会有这么多赞。我认为值得提及的细节是“你的陈述是否对位操作有任何影响?”或者也许值得编辑回答。 - user1767754
@user1767754 说得好,这也是为什么我将此问题关闭为另一个提供正确答案的问答的重复。 - Antti Haapala -- Слава Україні
请注意,随着Python 3中int类型的简化,sys.maxint已经过时。 在我的Python 3.8中甚至不存在。 - Mark Ransom

25

看起来我找到了更简单的解决方案,可以实现预期的效果:

uint8: x ^ 0xFF
uint16: x ^ 0xFFFF
uint32: x ^ 0xFFFFFFFF
uint64: x ^ 0xFFFFFFFFFFFFFFFF

8
您也可以使用无符号整数(例如来自numpy包)来实现预期的行为。
>>> import numpy as np
>>> bin( ~ np.uint8(1))
'0b11111110'

0
问题在于应用~运算符所得到的结果所表示的数字并没有明确定义,因为它取决于用于表示原始值的位数。例如:
5 = 101
~5 = 010 = 2

5 = 0101
~5 = 1010 = 10

5 = 00101
~5 = 11010 = 26

然而,~5的二进制补码在所有情况下都是相同的:

two_complement(~101) = 2^3 - 2 = 6
two_complement(~0101) = 2^4 - 10 = 6
two_complement(~00101) = 2^5 - 26 = 6

考虑到二进制补码用于表示负数值,因此将~5视为其补码的负值-6是有意义的。

更正式地说,我们得到以下结果:

  1. 翻转0和1(相当于取反)
  2. 取二进制补码
  3. 应用负号

如果x是一个n位数字:

~x = - two_complement(one_complement(x)) = - two_complement(2^n - 1 - x) = - (2^n - (2^n - 1 - x)) = - (x + 1)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接