从2 uint64值中提取Spooky-hash 128位值

问题描述 投票:2回答:3

我正在我正在构建的一个应用程序上实现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位整数 - 任何指针都将有助于理解这一点。

python c++ hash
3个回答
2
投票

该代码使用12351582206331609335获取任意unsigned char数组并将其转换为整数。从227846475865583962700201584165695002838你可以看到为什么调用代码包括从unsupported function from the Python C-APIdefinition 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 *),而16nlittle_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
投票

它执行从2个64位数中获取128位数所需的算术运算:

  • 将第1个(最重要的)一个64位向左移动
  • 添加第二个

换句话说,它连接起来。

示例(请注意,您按相反的顺序列出了数字):

PyLong_SHIFT

这是可能的,因为Python整数是无限的(或更好:受最大可用内存块的限制),正如>>> 12579423875165067478 | 12351582206331609335 << 64 227846475865583962700201584165695002838 所述:

整数具有无限的精度。


1
投票

将这些数字转换为十六进制,您将看到连接:

>>> 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位值。

© www.soinside.com 2019 - 2024. All rights reserved.