问题 Python中的高效任意大小的整数打包


struct.pack()函数允许将最多64位的整数转换为字节串。打包更大整数的最有效方法是什么?我宁愿不添加像PyCrypto这样的非标准模块的依赖(它提供了num_to_bytes())。


9763
2018-04-22 14:36


起源

不确定你在说什么。你想把Python的字符串版本长到一个结构吗? long的字符串版本是一个字符串;你打包就像任何其他字符串。你真正的问题是什么? - S.Lott
OP希望尽可能有效地将任意大小的整数打包成合理的字节串表示。 - Mike Boers


答案:


你的意思是 某物 喜欢这个:

def num_to_bytes(num):
    bytes = []
    num = abs(num) # Because I am unsure about negatives...
    while num > 0:
        bytes.append(chr(num % 256))
        num >>= 8
    return ''.join(reversed(bytes))

def bytes_to_num(bytes):
    num = 0
    for byte in bytes:
        num <<= 8
        num += ord(byte)
    return num

for n in (1, 16, 256, 257, 1234567890987654321):
    print n,
    print num_to_bytes(n).encode('hex'),
    print bytes_to_num(num_to_bytes(n))

哪个回报:

1 01 1
16 10 16
256 0100 256
257 0101 257
1234567890987654321 112210f4b16c1cb1 1234567890987654321

我只是不确定如何处理否定因素...我不熟悉那些喋喋不休。

编辑: 另一种解决方案(我的测试运行速度提高了约30%):

def num_to_bytes(num):
    num = hex(num)[2:].rstrip('L')
    if len(num) % 2:
        return ('0%s' % num).decode('hex')
    return num.decode('hex')

def bytes_to_num(bytes):
    return int(bytes.encode('hex'), 16)

5
2018-04-22 15:18



是的,这就是我的意思,我刚才写了这样一个函数。但是我想要一些更容易“移植”的东西,比如列表理解技巧或(更好)我不知道的库函数。 - noamtm
我刚刚发布了内置的int / hex转码和hex / str转换的另一个...它也快一点了! - Mike Boers
@noamtm:请用其他事实更新问题。不要只是在评论中隐藏信息。 - S.Lott


假设海报想要将大整数打包为二进制字符串,即不在数字中每个数字使用一个字节的存储空间。这样做的一种方法似乎是:

import marshal

a = 47L
print marshal.dumps(a)

这打印:

'l\x01\x00\x00\x00/\x00'

我不能说我理解如何解释这些位,现在......


3
2018-04-22 14:52



这是正确的方向,但前面仍然有两个冗余字节。 - Mike Boers
@Mike:实际上不止于此 - 我认为“l”和前4位只是内容的计数,后跟一个字节(“/”== chr(47)),最后是空。它看起来像marshal正在做一个更复杂的编码方案,只包括原始字节(例如看转储(2 ** 64-1)而不是中间的0x7f字节。 - Brian


这有点hacky,但你可以通过十六进制字符串表示,并使用十六进制编解码器二进制:

>>> a = 2**60
>>> a
1152921504606846976L
>>> hex(a)
'0x1000000000000000L'
>>> hex(a).rstrip("L")[2:].decode('hex')
'\x10\x00\x00\x00\x00\x00\x00\x00'       # 8bytes, as expected.

>>> int(_.encode('hex'), 16)
1152921504606846976L

它打破了一点因为十六进制编解码器需要偶数个数字,所以你需要填充它,你需要设置一个标志来处理负数。这是一个通用包/解包:

def pack(num):
    if num <0:
       num = (abs(num) << 1) | 1    # Hack - encode sign as lowest bit.
    else:
       num = num << 1
    hexval = hex(num).rstrip("L")[2:]
    if len(hexval)%2 ==1: hexval = '0' + hexval
    return hexval.decode('hex')

def unpack(s):
    val = int(s.encode('hex'), 16)
    sign = -1 if (val & 1) else 1
    return sign * (val>>1)


for i in [10,4534,23467, 93485093485, 2**50, 2**60-1, -1, -20, -2**60]:
    assert unpack(pack(i)) == i

由于需要填充所有的填充物,我不确定它比手动解决方案要好得多。


2
2018-04-22 16:17





正如S.Lott在评论中所建议的那样,只需将数字转换为字符串并打包该字符串即可。例如,

x = 2 ** 12345
struct.pack("40s", str(x))

1
2018-04-22 14:46



由于用空字节填充int,这甚至比仅存储整数的字符串表示更有效。 - Mike Boers
问题是不清楚需要什么类型的效率?我们正在寻找最有效的空间利用吗?或者包装结构的处理时间最短?就此而言,效率甚至是一个大问题?问题要求最有效的答案,但人们往往过早地进行优化。 - Eli Courtwright


我认为你的意思是你只想使用尽可能多的字节代表数字?例如如果数字是:

  • 255或更少你只使用1个字节
  • 65535或更少2个字节
  • 16777215或更少3个字节
  • 等等

在Psion PDA上,他们通常会有一些打包方案,您可以在其中读取第一个字节,检测它是否具有最高位设置,然后读取另一个字节(如果有)。这样你就可以继续读取字节,直到你读到“完整”数字。如果您处理的大多数数字都相当小,那么该系统运行良好,因为您通常每个数字只使用一个或两个字节。

另一种方法是使用一个(或多个)字节表示所使用的总字节数,但此时它基本上是Python中的一个字符串。即它是一串256位的基数。


1
2018-04-22 15:12