【发布时间】:2020-04-15 11:03:37
【问题描述】:
当使用 pythons inverse ~ 时,似乎这些位没有像我预期的那样翻转。我相信困惑在于我对 python 如何使用 2 的恭维存储数字的理解。
myInt = 5 #101
inverse = ~myInt
print(bin(inverse))
输出:-0b110
预期:-0b010 或 -0b10
【问题讨论】:
当使用 pythons inverse ~ 时,似乎这些位没有像我预期的那样翻转。我相信困惑在于我对 python 如何使用 2 的恭维存储数字的理解。
myInt = 5 #101
inverse = ~myInt
print(bin(inverse))
输出:-0b110
预期:-0b010 或 -0b10
【问题讨论】:
这不是 ~ 运算符的问题,它可以做它应该做的事情,而是使用您用来显示结果的 bin 函数。
在 Python 中,与大多数计算机系统一样,负整数在内部以“二进制补码”二进制表示形式存储。这意味着-1 由所有1 位的序列表示,并且每个较低的整数通过正常的整数减法规则修改该值。所以-2 是通过从-1 中减去1 形成的,你会得到一堆1 位,最后一位是零。
以下是一些数字及其 4 位二进制补码表示:
0 : 0000
1 : 0001
2 : 0010
5 : 0101
-1 : 1111 # this is ~0
-2 : 1110 # this is ~1
-3 : 1101 # this is ~2
-6 : 1010 # this is ~5
与许多其他语言不同,Python 的整数没有预定义的位长。它们不是 16 位或 32 位长,就像 C 中的 short 和 long 整数一样。相反,它们是动态调整大小的,根据需要添加更多位以表示越来越大的数字。当您需要将二进制数字表示为文本时(如 bin 函数所做的那样),这会导致一个棘手的情况。如果您知道您的号码仅使用 16 位,您可以每次都写出一个 16 位的字符串,但动态大小的号码需要不同的解决方案。
确实,Python 在bin 函数中做了一些不同的事情。正数用表示其值所需的最短位数写入。负数不是用二进制补码写的(它们实际上是在内部编码的方式),而是在它们的绝对值的位表示之前放一个减号。
所以你得到一个这样的表,其中按位补码不明显:
0 : 0b0
1 : 0b1
2 : 0b10
5 : 0b101
-1 : -0b1
-2 : -0b10
-3 : -0b11
-6 : -0b110
至于如何获得第一个表中的负数的二进制表示,唯一的好方法是选择大于任何数字的 2 的幂,并将其添加到之前的所有负值格式:
MY_MAXINT = 2**4
for v in [0,1,2,5,-1,-2,-3,-6]:
if v < 0:
v += MY_MAXINT
print(format(v, '04b'))
【讨论】:
这与two's complement 编码负数的方式有关。任何整数的负数表示都是通过反转数字和加一来计算的。
如果您翻转该逻辑,反转二进制表示将否定该值并减去一个。所以 5 的倒数确实应该是 -6。
~5
# -6
由于 Python 不为每个整数使用固定位数,因此无法显示所有前导零(有无限个数)。所以前面有一个负号,-0b110 代表-6。
选择任意固定位数,你可以写出不带负数的二进制数。例如使用 8 位(一个字节),它将是 1111 1010,这与您预期的相反。
【讨论】:
由于 Python 具有任意大小的有符号整数,因此从概念上讲,它们被符号扩展为无穷大。当我们反转0b101 位时,我们得到0b010,但符号扩展也被翻转:数字以无穷大1s 开头,而不是0s。而这个1111111....1010 的值等于-6,而不是-2——因为全1 的值是-1,然后我们去掉4 位和1 位。
一般来说,Python 整数的~x 等于-x - 1(等效于-(x+1))。这对于 Python 代码很少有用,但你永远不知道 :)
【讨论】:
你是对的 - 这种行为确实与 Python 在 twos-complement 中存储负数的方式有关。在幕后,否定一个数字,将(有效)无限的前导列表附加到二进制表示中。因此,虽然bin(6) 是0b110,但bin(-6) 实际上是0b010(您可以通过观察bin(-6 & 7) = 0b010 来了解这一点。
这意味着使用~ 做了两件事;它翻转二进制表示的第一位,然后再次翻转所有位。
【讨论】: