Python中十六进制数的二补数
下面的 a 和 b 是十六进制数,表示的是带符号的二进制数,采用的是补码形式。举个例子:
a = 0x17c7cc6e
b = 0xc158a854
现在我想知道 a 和 b 在十进制下的带符号表示。抱歉,我是个低级程序员,对 Python 还不太熟悉;问这个问题让我觉得很笨。我不在乎额外的库,但答案应该简单明了。背景是:a 和 b 是从一个 UDP 数据包中提取的数据。我无法控制格式。所以请不要给我一个假设我可以提前更改这些变量格式的答案。
我已经用以下方式将 a 和 b 转换成了:
aBinary = bin(int(a, 16))[2:].zfill(32) => 00010111110001111100110001101110 => 398969966
bBinary = bin(int(b, 16))[2:].zfill(32) => 11000001010110001010100001010100 => -1051154348
我试着做这样的事情(但不成功):
if aBinary[1:2] == 1:
aBinary = ~aBinary + int(1, 2)
在 Python 中正确的做法是什么?
7 个回答
另一种现代的解决方案:
>>> h = 0xc158a854
>>> int.from_bytes(bytes.fromhex(hex(h)[2:]), byteorder='big', signed=True)
-1051154348
>>> import numpy
>>> numpy.int32(0xc158a854)
-1051154348
当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。
在Python中,有一种很好的方法可以使用位运算来实现这个功能。比如,对于32位的值:
def s32(value):
return -(value & 0x80000000) | (value & 0x7fffffff)
将这个方法应用到你的值上:
>>> s32(a)
398969966
>>> s32(b)
-1051154348
这个函数的作用是对值进行符号扩展,这样它就能正确地被解释为带有正确符号和数值。
Python有点特别,因为它使用的是任意精度的整数,所以负数会被处理成好像前面有无数个1位的样子。例如:
>>> bin(-42 & 0xff)
'0b11010110'
>>> bin(-42 & 0xffff)
'0b1111111111010110'
>>> bin(-42 & 0xffffffff)
'0b11111111111111111111111111010110'
为什么不使用 ctypes 呢?
>>> import ctypes
>>> a = 0x17c7cc6e
>>> ctypes.c_int32(a).value
398969966
>>> b = 0xc158a854
>>> ctypes.c_int32(b).value
-1051154348
你需要至少知道你的数据的宽度。例如,0xc158a854有8个十六进制数字,所以它至少得是32位宽;看起来它是一个无符号的32位值。我们可以通过一些位运算来处理它:
In [232]: b = 0xc158a854
In [233]: if b >= 1<<31: b -= 1<<32
In [234]: b
Out[234]: -1051154348L
这里的L表示Python 2已经开始将这个值当作长整型来处理;这通常不太重要,但在这种情况下说明我处理的值超出了这个安装环境中常见的整型范围。从二进制结构(比如UDP数据包)中提取数据的工具是struct.unpack;如果你一开始就告诉它你的值是有符号的,它就会给你正确的值:
In [240]: s = '\xc1\x58\xa8\x54'
In [241]: import struct
In [242]: struct.unpack('>i', s)
Out[242]: (-1051154348,)
这假设使用的是二的补码表示法;还有一些不太常见的数字编码方式,比如一的补码(例如UDP中使用的校验和)、符号和大小、或者IEEE 754浮点数。