14

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

4

7 回答 7

7

遇到了同样的问题。从 python 3.2 开始,您可以使用int.to_bytes

>>> (2**100).to_bytes(16, byteorder='big')
b'\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
于 2018-11-02T13:40:54.520 回答
5

你的意思这样的:

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)
于 2009-04-22T15:18:32.300 回答
4

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

import marshal

a = 47L
print marshal.dumps(a)

这打印:

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

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

于 2009-04-22T14:52:01.430 回答
2

我认为您的意思是您只想使用表示数字所需的字节数?例如,如果数字是:

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

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

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

于 2009-04-22T15:12:39.447 回答
2

这有点 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

尽管需要对填充等进行所有摆弄,但我不确定它是否比手动解决方案好得多。

于 2009-04-22T16:17:09.213 回答
0

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

x = 2 ** 12345
struct.pack("40s", str(x))
于 2009-04-22T14:46:59.147 回答
0

这概括了Claudio 的先前答案。它尝试使用最佳字节长度。它还支持无符号和有符号整数。

def int_to_bytes(i: int, *, signed: bool = False) -> bytes:
    length = (i.bit_length() + 7 + signed) // 8
    return i.to_bytes(length, byteorder='big', signed=signed)

def bytes_to_int(b: bytes, *, signed: bool = False) -> int:
    return int.from_bytes(b, byteorder='big', signed=signed)

# Test unsigned:
for i in range(1025):
    assert i == bytes_to_int(int_to_bytes(i))

# Test signed:
for i in range(-1024, 1025):
    assert i == bytes_to_int(int_to_bytes(i, signed=True), signed=True)
于 2019-01-11T07:19:48.890 回答