我正在构建的一个应用程序上实现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个单位是12579423875165067478
和12351582206331609335
python 128整数是:
227846475865583962700201584165695002838
但是如何从2个64个uint派生出128位整数-任何指针都将有助于理解这一点。
答案 0 :(得分:2)
它执行从2个 64bit 中获取 128bit 编号所需的算术运算:
换句话说,它将它们串联起来。
示例(请注意,您以相反的顺序列出了数字):
>>> 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 *)
强制转换的目的。呼叫传入16
到n
,little_endian
设置为1
,is_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位值。