我正在我正在构建的一个应用程序上实现Spooky-hash。
我正在引用Golang和C库。它们以2个无符号64位整数的形式提供输出。
在查看python implementation(它是C ++的包装器)实现时,他们得到了一个128个大数字并给出了答案。
我的问题是,python使用2个64uint值来获取这个数字是什么?
我认为这是相关的C ++代码(来自python包装器),它调用原始的C ++库:
static PyObject *
spooky_hash128(PyObject *self, PyObject *args, PyObject *kwargs)
{
const char *message;
int message_length;
uint64 seed[2] = {0};
static char *kwlist[] = {(char *)"message", (char *)"seed",
NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|K", kwlist,
&message, &message_length, &seed)) {
return NULL;
}
seed[1] = seed[0];
SpookyHash::Hash128(message, message_length, &seed[0], &seed[1]);
PyObject *retval = _PyLong_FromByteArray((unsigned char *)seed, 16, 1, 0);
return retval;
}
所以对于像这样的字符串
15496-17156-0228-a1c731ea-289b-dcf3-a5d8-afb9b6ba34609-5aba2fe5-54ff-098e-c0eb-457
正确的2 64个单位是qazxsw poi和qazxsw poi
python 128整数是:12579423875165067478
但是如何从2 64个uint派生的128位整数 - 任何指针都将有助于理解这一点。
该代码使用12351582206331609335
获取任意unsigned char数组并将其转换为整数。从227846475865583962700201584165695002838
你可以看到为什么调用代码包括从unsupported function from the Python C-API到definition of _PyLong_FromByteArray()
的演员:
uint64[]
因此,不是采用两个64位数字,而是传递16个8位数字,这就是char[]
演员所用的数字。该调用在PyObject *
_PyLong_FromByteArray(const unsigned char* bytes, size_t n,
int little_endian, int is_signed)
中传递给(unsigned char *)
,而16
在n
和little_endian
中为0。
在Python代码中,您可以使用1
执行相同的操作;将两者都转换为长度为8的字节,little-endian(因为SpookyHash C ++参考实现是为64位little-endian体系结构明确设计的):
is_signed
每个字节是最终数字的一个分量,是256的幂的倍数。最低有效字节乘以int.to_bytes()
method,下一个乘以>>> 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在上面,右边的171是最重要的,是功率15的176倍256。
您可以自己执行以下操作,在Python代码中重新创建数字:
256 ** 0
产生预期的输出:
256 ** 1
除了CPU使用value = 0
for i, b in enumerate(bytevalue):
value += b * (256 ** i)
来实现这一点;将值向左移8位与将其相乘256相同,并且重复应用此类移位会将该值乘以256的幂。如果从最高有效字节开始并保持移位值,则 - 在包含下一个字节之前向左移8位(使用按位OR),你得到相同的输出:
>>> bytevalue = (12579423875165067478).to_bytes(8, 'little') + (12351582206331609335).to_bytes(8, 'little')
>>> for i, b in enumerate(bytevalue):
... value += b * (256 ** i)
...
>>> value
227846475865583962700201584165695002838
为了避免反转,您可以将当前字节移位到组合之前已累积的位数:
bit-shifting
这就是>>> value = 0
>>> for b in reversed(bytevalue):
... value = value << 8 | b
...
>>> value
227846475865583962700201584165695002838
实际实际使用的内容。但是,Python >>> accumbits = 0
>>> for b in bytevalue:
... value |= (b << accumbits)
... accumbits += 8
...
>>> value
227846475865583962700201584165695002838
值的内部结构实际上将大整数分成多个30位或15位“块”,因此任意大的整数值可以适合固定大小的C整数,这就是为什么函数也使用了一些使用_PyLong_FromByteArray
进行额外的测试和转换。
所有这一切都归结为两个64位输入值被端到端地放置在内存中以形成一个长128位的数字;第二个数字右边的第一个数字(最不重要)(更重要),所以在Python代码中你可以将第二个数字64位向左移动并将结果附加到第一个数字:
int
它执行从2个64位数中获取128位数所需的算术运算:
换句话说,它连接起来。
示例(请注意,您按相反的顺序列出了数字):
PyLong_SHIFT
这是可能的,因为Python整数是无限的(或更好:受最大可用内存块的限制),正如>>> 12579423875165067478 | 12351582206331609335 << 64
227846475865583962700201584165695002838
所述:
整数具有无限的精度。
将这些数字转换为十六进制,您将看到连接:
>>> ui64_0 = 12579423875165067478
>>> ui64_1 = 12351582206331609335
>>>
>>> ui128_0 = (ui64_1 << 64) + ui64_0
>>> ui128_0
227846475865583962700201584165695002838
让我们更详细地看一下:
[Python 3.Docs]: Numeric Types - int, float, complex
该128位数字只分为两个64位值。