为啥 Python 的无穷大哈希有 π 的数字?
Posted
技术标签:
【中文标题】为啥 Python 的无穷大哈希有 π 的数字?【英文标题】:Why does Python's hash of infinity have the digits of π?为什么 Python 的无穷大哈希有 π 的数字? 【发布时间】:2019-10-07 04:57:25 【问题描述】:Python 中无穷大的哈希值与pi 匹配:
>>> inf = float('inf')
>>> hash(inf)
314159
>>> int(math.pi*1e5)
314159
这只是巧合还是故意的?
【问题讨论】:
不确定,但我猜这就像hash(float('nan'))
成为0
一样故意。
嗯,sys.hash_info
中没有提及。复活节彩蛋?
问问蒂姆·彼得斯。这是他在 19 年前引入这个常量的提交:github.com/python/cpython/commit/…。当我重新处理 bugs.python.org/issue8188 中的数字哈希时,我保留了这些特殊值。
@MarkDickinson 谢谢。看起来 Tim 最初可能也使用了 e 的数字作为 -inf 的哈希值。
@wim 是的,是的。显然我把它改成了-314159
。我忘记了。
【参考方案1】:
总结:这不是巧合; _PyHASH_INF
is hardcoded as 314159 在 Python 的默认 CPython 实现中,并被选为任意值(显然来自 π 的数字)by Tim Peters in 2000。
hash(float('inf'))
的值是数字类型内置哈希函数的系统相关参数之一,is also available 在 Python 3 中为 sys.hash_info.inf
:
>>> import sys
>>> sys.hash_info
sys.hash_info(width=64, modulus=2305843009213693951, inf=314159, nan=0, imag=1000003, algorithm='siphash24', hash_bits=64, seed_bits=128, cutoff=0)
>>> sys.hash_info.inf
314159
(同样的结果with PyPy。)
在代码方面,hash
是一个内置函数。在 Python 浮点对象上调用它会调用其指针由内置浮点类型 (PyTypeObject PyFloat_Type
) 的tp_hash
attribute 给出的函数,其中is 是float_hash
函数,defined 为return _Py_HashDouble(v->ob_fval)
,反过来has
if (Py_IS_INFINITY(v))
return v > 0 ? _PyHASH_INF : -_PyHASH_INF;
其中_PyHASH_INF
是defined as 314159:
#define _PyHASH_INF 314159
就历史而言,314159
在 Python 代码(您可以通过 git bisect
或 git log -S 314159 -p
找到)中首次提及 314159
是由 Tim Peters 在 2000 年 8 月添加的,现在是在cpython
git 存储库中提交39dce293。
提交信息说:
修复http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470。 这是一个误导性的错误——真正的“错误”是
hash(x)
给出了一个错误 当x
是无穷大时返回。解决了这个问题。添加了新的Py_IS_INFINITY
宏到pyport.h
。重新排列代码以减少浮点和散列中不断增长的重复 复数,将特伦特早先的尝试推向了一个合乎逻辑的结论。 修复了极其罕见的错误,即使存在浮点数的散列也可能返回 -1 不是错误(没有浪费时间尝试构建测试用例,它只是 从代码中可以明显看出它可能发生)。改进了复杂的哈希,以便hash(complex(x, y))
不再系统地等于hash(complex(y, x))
。
特别是,在这个提交中,他在Objects/floatobject.c
中删除了static long float_hash(PyFloatObject *v)
的代码,并将其改为return _Py_HashDouble(v->ob_fval);
,并在Objects/object.c
中long _Py_HashDouble(double v)
的定义中添加了以下几行:
if (Py_IS_INFINITY(intpart))
/* can't convert to long int -- arbitrary */
v = v < 0 ? -271828.0 : 314159.0;
如前所述,这是一个随意的选择。请注意,271828 由e 的前几位小数组成。
相关的后续提交:
By Mark Dickinson in Apr 2010 (also),使Decimal
类型的行为相似
By Mark Dickinson in Apr 2010 (also),将此检查移至顶部并添加测试用例
By Mark Dickinson in May 2010 为issue 8188,将散列函数完全重写为its current implementation,但保留了这种特殊情况,为常量命名为_PyHASH_INF
(同时删除了271828,这就是为什么在Python 3 中@ 987654378@ 返回 -314159
而不是 -271828
,就像它在 Python 2 中所做的那样)
By Raymond Hettinger in Jan 2011,在sys.hash_info
的 Python 3.2 的“新增功能”中添加了一个显式示例,显示了上述值。 (见here。)
By Stefan Krah in Mar 2012 修改 Decimal 模块但保留此哈希。
By Christian Heimes in Nov 2013,将_PyHASH_INF
的定义从Include/pyport.h
移至现在所在的Include/pyhash.h
。
【讨论】:
-Inf 选择 -271828 消除了 pi 关联是偶然的任何疑问。 @RussellBorogove 不,但它降低了大约一百万倍的可能性;) @cmaster:参见上面写着 2010 年 5 月的部分,即 hashing of numeric types 和 issue 8188 的文档部分——我们希望hash(42.0)
与 @987654386 相同@,也与hash(Decimal(42))
和hash(complex(42))
和hash(Fraction(42, 1))
相同。解决方案(由 Mark Dickinson 提出)是一个优雅的 IMO:定义一个适用于任何有理数的数学函数,并利用浮点数也是有理数这一事实。
@ShreevatsaR 啊,谢谢。虽然我不想保证这些平等,但很高兴知道对于看似复杂的代码有一个好的、可靠和合乎逻辑的解释:-)
@cmaster 整数的散列函数就是hash(n) = n % M
,其中 M = (2^61 - 1)。这对于有理 n 被推广到 hash(p/q) = (p/q) mod M
,除法被解释为模 M(换句话说:hash(p/q) = (p * inverse(q, M)) % M
)。我们想要这个的原因:如果在字典d
中输入d[x] = foo
,然后我们有x==y
(例如42.0==42)但d[y]
与d[x]
不同,那么我们就有一个问题。大多数看似复杂的代码来自浮点格式本身的性质,为了正确恢复分数并且需要 inf 和 NaN 值的特殊情况。【参考方案2】:
_PyHASH_INF
等于 defined as a constant 等于 314159
。
我找不到任何关于此的讨论,或者 cmets 给出原因。我认为它或多或少是任意选择的。我想只要他们不对其他哈希使用相同的有意义的值,就没有关系。
【讨论】:
小挑剔:根据定义,相同的值将用于其他哈希几乎是不可避免的,例如在这种情况下,hash(314159)
也是 314159
。也可以试试,在 Python 3 中,hash(2305843009214008110) == 314159
(这个输入是314159 + sys.hash_info.modulus
)等等。
@ShreevatsaR 我的意思是只要他们不选择这个值作为定义上其他值的哈希值,那么选择这样一个有意义的值就不会增加哈希冲突的机会【参考方案3】:
确实,
sys.hash_info.inf
返回314159
。该值不会生成,它内置在源代码中。
事实上,
hash(float('-inf'))
在 python 2 (it's -314159 now) 中返回 -271828
,或近似 -e。
历史上最著名的两个无理数被用作哈希值这一事实使得这不太可能是巧合。
【讨论】:
以上是关于为啥 Python 的无穷大哈希有 π 的数字?的主要内容,如果未能解决你的问题,请参考以下文章