Why does Python's hash of infinity have the digits of π?
Summary: It's not a coincidence; _PyHASH_INF
is hardcoded as 314159 in the default CPython implementation of Python, and was picked as an arbitrary value (obviously from the digits of π) by Tim Peters in 2000.
The value of hash(float('inf'))
is one of the system-dependent parameters of the built-in hash function for numeric types, and is also available as sys.hash_info.inf
in Python 3:
>>> 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
(Same results with PyPy too.)
In terms of code, hash
is a built-in function. Calling it on a Python float object invokes the function whose pointer is given by the tp_hash
attribute of the built-in float type (PyTypeObject PyFloat_Type
), which is the float_hash
function, defined as return _Py_HashDouble(v->ob_fval)
, which in turn has
if (Py_IS_INFINITY(v))
return v > 0 ? _PyHASH_INF : -_PyHASH_INF;
where _PyHASH_INF
is defined as 314159:
#define _PyHASH_INF 314159
In terms of history, the first mention of 314159
in this context in the Python code (you can find this with git bisect
or git log -S 314159 -p
) was added by Tim Peters in August 2000, in what is now commit 39dce293 in the cpython
git repository.
The commit message says:
Fix for http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470. This was a misleading bug -- the true "bug" was that
hash(x)
gave an error return whenx
is an infinity. Fixed that. Added newPy_IS_INFINITY
macro topyport.h
. Rearranged code to reduce growing duplication in hashing of float and complex numbers, pushing Trent's earlier stab at that to a logical conclusion. Fixed exceedingly rare bug where hashing of floats could return -1 even if there wasn't an error (didn't waste time trying to construct a test case, it was simply obvious from the code that it could happen). Improved complex hash so thathash(complex(x, y))
doesn't systematically equalhash(complex(y, x))
anymore.
In particular, in this commit he ripped out the code of static long float_hash(PyFloatObject *v)
in Objects/floatobject.c
and made it just return _Py_HashDouble(v->ob_fval);
, and in the definition of long _Py_HashDouble(double v)
in Objects/object.c
he added the lines:
if (Py_IS_INFINITY(intpart))
/* can't convert to long int -- arbitrary */
v = v < 0 ? -271828.0 : 314159.0;
So as mentioned, it was an arbitrary choice. Note that 271828 is formed from the first few decimal digits of e.
Related later commits:
By Mark Dickinson in Apr 2010 (also), making the
Decimal
type behave similarlyBy Mark Dickinson in Apr 2010 (also), moving this check to the top and adding test cases
By Mark Dickinson in May 2010 as issue 8188, completely rewriting the hash function to its current implementation, but retaining this special case, giving the constant a name
_PyHASH_INF
(also removing the 271828 which is why in Python 3hash(float('-inf'))
returns-314159
rather than-271828
as it does in Python 2)By Raymond Hettinger in Jan 2011, adding an explicit example in the "What's new" for Python 3.2 of
sys.hash_info
showing the above value. (See here.)By Stefan Krah in Mar 2012 modifying the Decimal module but keeping this hash.
By Christian Heimes in Nov 2013, moved the definition of
_PyHASH_INF
fromInclude/pyport.h
toInclude/pyhash.h
where it now lives.
_PyHASH_INF
is defined as a constant equal to 314159
.
I can't find any discussion about this, or comments giving a reason. I think it was chosen more or less arbitrarily. I imagine that as long as they don't use the same meaningful value for other hashes, it shouldn't matter.
Indeed,
sys.hash_info.inf
returns 314159
. The value is not generated, it's built into the source code.
In fact,
hash(float('-inf'))
returns -271828
, or approximately -e, in python 2 (it's -314159 now).
The fact that the two most famous irrational numbers of all time are used as the hash values makes it very unlikely to be a coincidence.