Подтвердить что ты не робот

Эффективная целая упаковка произвольного размера в Python

Функция struct.pack() позволяет преобразовывать целые числа с длиной до 64 бит в байтовые строки. Каков наиболее эффективный способ упаковки еще большего числа? Я бы предпочел не добавлять зависимость от нестандартных модулей, таких как PyCrypto (который предоставляет num_to_bytes()).

4b9b3361

Ответ 1

Наткнулся на ту же проблему. Начиная с 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'

Ответ 2

Вы имеете в виду что-то вроде этого:

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

Я просто не уверен, что делать с негативами... Я не знаком с бит twidling.

EDIT: Другое решение (которое на моих тестах работает примерно на 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)

Ответ 3

Предполагая, что плакат хочет упаковать большое целое число в виде двоичной строки, т.е. не использовать один байт памяти на цифру в номере. Один из способов сделать это:

import marshal

a = 47L
print marshal.dumps(a)

Отпечатки:

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

Я не могу сказать, что я понимаю, как интерпретировать эти биты, прямо сейчас...

Ответ 4

Это немного взломано, но вы можете пройти через шестнадцатеричное представление строки, а там в двоичный код с шестнадцатеричным кодеком:

>>> 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

С учетом всех возможностей для заполнения и т.д., я не уверен, что это намного лучше, чем ручное решение.

Ответ 5

Я так понимаю, вы хотите, чтобы вы только использовали столько байтов, сколько вам нужно, чтобы представить число? например если число:

  • 255 или менее вы используете только 1 байт
  • 65535 или менее 2 байта
  • 16777215 или менее 3 байта
  • и т.д.

На КПК Psion у них обычно есть схема упаковки, в которой вы читаете первый байт, обнаруживаете, имеет ли он самый старший бит, а затем читает другой байт, если он есть. Таким образом, вы просто будете читать байты, пока не будете читать "полный" номер. Эта система работает очень хорошо, если большая часть номеров, с которыми вы имеете дело, довольно мала, так как вы обычно используете только один или два байта на номер.

Альтернативой является наличие одного (или более) байтов, представляющего количество используемых суммарных байтов, но в этом случае в основном это строка в Python. то есть строку с базовыми 256 цифрами.

Ответ 6

Как было предложено С.Лоттом в комментарии, просто преобразуйте число в строку и упакуйте эту строку. Например,

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

Ответ 7

Это обобщает предыдущий ответ Клаудио. Он пытается использовать оптимальную длину в байтах. Он также поддерживает целые числа без знака и со знаком.

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)