众所周知,python中只有一种整数类型 int,并没有“无符号”的概念。导致有时我们需要使用对一些数据进行取反,python自动使用有符号的形式输出了,不符合预期。怎么解决这个问题呢?
问题现象
>>> bin(~0b00)
'-0b1'
>>> bin(~0b01)
'-0b10'
>>> bin(~0b10)
'-0b11'
>>> bin(~0b11)
'-0b100'
其实我只期望输出类似 0b11
0b10
之类的,并不需要这个符号,python这个自动以有符号数输出的转换就显得太多余了,这样的结果完全没法直接使用。
问题解决
问题的主要矛盾点就在于,不让python以负数补码形式展示。但 f-string
等方法都没法以无符号形式展示[1],因此无法通过自带方式解决。
尝试
那么,本质上是因为反码补码运算导致的显示问题,那我们可不可以直接通过相反操作来抵消这个补码运算呢?(死去的微机原理又开始攻击我)
举个例子,来说明负数运算原理:
5 = 4'b0101
取反得反码:4'b1010
加一得补码:4'b1011
得 -5的补码:4'b1011
那我想获取 bin(~5),实际上我获取 bin(~(5-1)) 不就可以了?
然而也不行,咱定睛一看,啊,原来之前举的例子不充分,python的显示逻辑是这样的:
>>> bin(5)
'0b101'
>>> bin(-5)
'-0b101'
所以实际显示的是负数的二进制数。想想也合理,怎么可能展示补码呢。
通过ctypes解决
既然python会自动转换,那么需要找一个机制,让它老老实实接收并输出原来的值,不要执行转换。自然就可以使用ctypes
来中转一下解决。
>>> import ctypes
>>> ctypes.c_uint32(~0b11)
c_ulong(4294967292)
>>> ctypes.c_uint32(~0b11).value
4294967292
>>> bin(ctypes.c_uint32(~0b11).value)
'0b11111111111111111111111111111100'
这样就相当于强制将负数转化成了无符号数,然后再通过value
方法取出,因为32位带了符号,python自动使用longlong
类型存储这个数据,所以输出了4294967292
。然后再调用bin
函数,就能输出取反后的数据了。
封装如下:
def unsigned_bitwise_not(v: int):
return bin(ctypes.c_uint32(~v).value)
通过numpy解决
>>> from numpy import uint32
>>> bin(~uint32(0b111))
'0b11111111111111111111111111111000
注意要先uint32再取反,否则会报错:
>>> bin(uint32(~0b111))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: Python integer -8 out of bounds for uint32
通过字符串解决
简单粗暴,直接上代码,实现0和1字符的互转
convert_dict = {'0': '1', '1': '0'}
def unsigned_bitwise_not(v: int):
[_, value] = bin(v).split('b')
return '0b' + ''.join([convert_dict[c] for c in value])
或:
def unsigned_bitwise_not(v: int):
[_, value] = bin(v).split('b')
return '0b' + ''.join(['1' if c == '0' else '0' for c in value])
或:
def unsigned_bitwise_not(v: int):
[_, value] = bin(v).split('b')
return '0b' + ''.join(list(map(lambda c: '1' if '0' == c else '0', value)))
通过与运算解决
通过与运算直接mask掉符号位即可,这也是最方便的方法
>>> bin(~0b011001 & 0xFFFF)
'0b1111111111100110'
>>>
>>> bin(~0b011001 & 0xFFF)
'0b111111100110'
就算是用完了所有可用位数也无所谓,这应该和python内部自动类型转换机制有关,会直接拓展到运算两侧最大的位数(包括符号位),所以符号位永远都被mask掉。(猜测)
>>> bin(~0b011001 & 0xFFFFFFFF)
'0b11111111111111111111111111100110'
关于这个问题,到时扒拉cpython源码看看。
[1] Python 文档 https://docs.python.org/3/library/string.html#format-specification-mini-language