Код использует неподдерживаемую функцию из Python C-API , чтобы взять произвольный массив unsigned char и преобразовать его в целое число. Из определения _PyLong_FromByteArray()
видно, почему код вызова включает приведение от uint64[]
до char[]
:
PyObject *
_PyLong_FromByteArray(const unsigned char* bytes, size_t n,
int little_endian, int is_signed)
Таким образом, вместо двух 64-битных чисел передаются 16 8-битных чисел, для чего предназначен (unsigned char *)
cast. Вызов проходит в 16
для n
, а little_endian
устанавливается на 1
и is_signed
на 0.
В коде Python вы можете сделать то же самое с int.to_bytes()
методом ; преобразовать оба байта длиной 8 с прямым порядком байтов (поскольку эталонная реализация SpookyHash C ++ явно предназначена для 64-разрядных архитектур с прямым порядком байтов):
>>> bytevalue = (12579423875165067478).to_bytes(8, 'little') + (12351582206331609335).to_bytes(8, 'little')
>>> bytevalue
b'\xd6\x18H\xa6]\x17\x93\xae\xf7`n>\x93\xa2i\xab'
>>> list(bytevalue)
[214, 24, 72, 166, 93, 23, 147, 174, 247, 96, 110, 62, 147, 162, 105, 171]
Каждый байт является компонентом конечного числа, кратным степени 256. Младший значащий байт умножается на 256 ** 0
, следующий на 256 ** 1
и т. Д. В системе с прямым порядком байтов самый младший число идет первым (то есть 256 к значению степени 0), и в приведенном выше значении 171 справа является наиболее значимым, будучи 171 от 256 до степени 15.
Вы можете воссоздать число в коде Python, выполнив это самостоятельно:
value = 0
for i, b in enumerate(bytevalue):
value += b * (256 ** i)
, который выдает ожидаемый результат:
>>> bytevalue = (12579423875165067478).to_bytes(8, 'little') + (12351582206331609335).to_bytes(8, 'little')
>>> for i, b in enumerate(bytevalue):
... value += b * (256 ** i)
...
>>> value
227846475865583962700201584165695002838
за исключением процессоров, использующих битовое смещение для достижения этого; сдвиг значения на 8 бит влево - это то же самое, что умножение его на 256, и повторное применение таких сдвигов умножит значение на степень 256. Если вы начали с старшего байта и продолжали сдвигать значение так, -дальше влево на 8 битов перед включением следующего байта (используя побитовое ИЛИ) вы получите такой же вывод:
>>> value = 0
>>> for b in reversed(bytevalue):
... value = value << 8 | b
...
>>> value
227846475865583962700201584165695002838
Чтобы избежать реверса, вы можете сдвинуть текущий байт на количество битов, уже накопленных до объединения:
>>> accumbits = 0
>>> for b in bytevalue:
... value |= (b << accumbits)
... accumbits += 8
...
>>> value
227846475865583962700201584165695002838
Вот что на самом деле использует _PyLong_FromByteArray
Реализация. Однако внутренняя структура значения Python int
фактически разбивает большие целые числа на несколько 30-битных или 15-битных «кусков», поэтому произвольно большие целочисленные значения могут быть помещены в целые числа C фиксированного размера, поэтому функция также использует дополнительное тестирование и сдвиги с PyLong_SHIFT
.
Все это сводится к двум 64-битным входным значениям, помещаемым сквозным в память для формирования длинного 128-битного числа; первое число (наименее значимое) справа от второго числа (более значимое), поэтому в коде Python вы можете просто сдвинуть второе число на 64 бита влево и прикрепить результат к первому:
>>> 12579423875165067478 | 12351582206331609335 << 64
227846475865583962700201584165695002838