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

时间:2019-04-23 11:25:40

标签: python c++ hash

我正在构建的一个应用程序上实现Spooky-hash

我正在引用Golang和C库。它们以2个无符号64位整数的形式提供输出int。

在查看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个单位是1257942387516506747812351582206331609335

python 128整数是: 227846475865583962700201584165695002838

但是如何从2个64个uint派生出128位整数-任何指针都将有助于理解这一点。

3 个答案:

答案 0 :(得分:2)

它执行从2个 64bit 中获取 128bit 编号所需的算术运算:

  • 将1 st (最高有效)向左移 64 位一位
  • 添加第二个 nd 一个

换句话说,它将它们串联起来。

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

>>> ui64_0 = 12579423875165067478
>>> ui64_1 = 12351582206331609335
>>>
>>> ui128_0 = (ui64_1 << 64) + ui64_0
>>> ui128_0
227846475865583962700201584165695002838

这是可能的,因为 Python 整数是无限制的(或更优:受最大可用内存块的限制),如[Python 3.Docs]: Numeric Types - int, float, complex所述:

  

整数具有无限精度。

答案 1 :(得分:2)

代码使用unsupported function from the Python C-API来获取任意 unsigned char 数组并将其转换为整数。从definition of _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 *)强制转换的目的。呼叫传入16nlittle_endian设置为1is_signed设置为0。

在Python代码中,您可以使用int.to_bytes() method执行相同的操作;都将它们都转换为长度为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是最高有效的,是256次幂15的171倍。

您可以自己执行以下操作在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

除CPU使用bit-shifting来实现此目的外;将值向左移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

答案 2 :(得分:1)

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

12579423875165067478 = AE93175DA64818D6h
12351582206331609335 = AB69A2933E6E60F7h

227846475865583962700201584165695002838 = AB69A2933E6E60F7AE93175DA64818D6h

让我们详细了解一下:

227846475865583962700201584165695002838 = AB69A2933E6E60F7 AE93175DA64818D6h

该128位数字被分为两个64位值。