¿Por qué el hash de infinito de Python tiene los dígitos de π?


241

El hash del infinito en Python tiene dígitos que coinciden con pi :

>>> inf = float('inf')
>>> hash(inf)
314159
>>> int(math.pi*1e5)
314159

¿Es solo una coincidencia o es intencional?


99
No estoy seguro, pero supongo que es tan deliberado como hash(float('nan'))ser 0.
cs95

1
Hmm, no se menciona eso en sys.hash_info. ¿Huevo de Pascua?
wim

123
Pregúntale a Tim Peters. Aquí está el commit donde introdujo esta constante, hace 19 años: github.com/python/cpython/commit/… . Mantuve esos valores especiales cuando volví a trabajar el hash numérico en bugs.python.org/issue8188
Mark Dickinson

8
@ MarkDickinson Gracias. Parece que Tim también pudo haber usado originalmente los dígitos de e para el hash de -inf.
wim

17
@wim Ah sí, es cierto. Y aparentemente cambié eso a -314159. Me había olvidado de eso.
Mark Dickinson

Respuestas:


47

_PyHASH_INFse define como una constante igual a 314159.

No puedo encontrar ninguna discusión sobre esto, o comentarios que den una razón. Creo que fue elegido más o menos arbitrariamente. Me imagino que mientras no usen el mismo valor significativo para otros hashes, no debería importar.


66
Nitpick pequeño: es casi inevitable por definición que se use el mismo valor para otros hashes, por ejemplo, en este caso hash(314159)también 314159. También intente, en Python 3, hash(2305843009214008110) == 314159(esta entrada es 314159 + sys.hash_info.modulus) etc.
ShreevatsaR

3
@ShreevatsaR Solo quise decir que mientras no elijan este valor como el hash de otros valores por definición, entonces elegir un valor significativo como este no aumenta la posibilidad de colisiones de hash
Patrick Haugh,

220

Resumen: no es una coincidencia; _PyHASH_INFestá codificado como 314159 en la implementación predeterminada de Python para CPython, y Tim Peters lo eligió como un valor arbitrario (obviamente de los dígitos de π) en 2000 .


El valor de hash(float('inf'))es uno de los parámetros dependientes del sistema de la función hash incorporada para los tipos numéricos, y también está disponible como sys.hash_info.infen 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

(Los mismos resultados con PyPy también).


En términos de código, hashes una función incorporada. Al llamarlo en un objeto flotante de Python se invoca la función cuyo puntero viene dado por el tp_hashatributo del tipo flotante incorporado ( PyTypeObject PyFloat_Type), que es la float_hashfunción, definida como return _Py_HashDouble(v->ob_fval), que a su vez tiene

    if (Py_IS_INFINITY(v))
        return v > 0 ? _PyHASH_INF : -_PyHASH_INF;

donde _PyHASH_INFse define como 314159:

#define _PyHASH_INF 314159

En términos de historia, la primera mención de 314159este contexto en el código Python (puede encontrar esto con git bisecto git log -S 314159 -p) fue agregada por Tim Peters en agosto de 2000, en lo que ahora se confirma 39dce293 en el cpythonrepositorio git.

El mensaje de confirmación dice:

Arreglo para http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470 . Este fue un error engañoso: el verdadero "error" fue el que hash(x)devolvió el error cuando xes infinito. Arreglado eso. Se agregó una nueva Py_IS_INFINITYmacro a pyport.h. Código reordenado para reducir la creciente duplicación en el hash de números flotantes y complejos, empujando la puñalada anterior de Trent a una conclusión lógica. Se corrigió un error extremadamente raro en el que el hash de los flotadores podía devolver -1 incluso si no había un error (no perdía el tiempo tratando de construir un caso de prueba, era simplemente obvio por el código que podía suceder). Hash complejo mejorado para que hash(complex(x, y))ya no sea igual sistemáticamente hash(complex(y, x)).

En particular, en esta confirmación, eliminó el código de static long float_hash(PyFloatObject *v)in Objects/floatobject.cy lo hizo justo return _Py_HashDouble(v->ob_fval);, y en la definición de long _Py_HashDouble(double v)in Objects/object.cagregó las líneas:

        if (Py_IS_INFINITY(intpart))
            /* can't convert to long int -- arbitrary */
            v = v < 0 ? -271828.0 : 314159.0;

Como se mencionó, fue una elección arbitraria. Tenga en cuenta que 271828 se forma a partir de los primeros dígitos decimales de e .

Compromisos posteriores relacionados:


44
La elección de -271828 para -Inf elimina cualquier duda de que la asociación pi fue accidental.
Russell Borogove

24
@RussellBorogove No, pero hace que sea aproximadamente un millón de veces menos probable;)
tubería

8
@cmaster: Vea la parte anterior donde dice mayo de 2010, es decir, la sección de documentación sobre hashing de tipos numéricos y el problema 8188 : la idea es que queremos hash(42.0)ser iguales hash(42), también iguales a hash(Decimal(42))y hash(complex(42))y hash(Fraction(42, 1)). La solución (por Mark Dickinson) es elegante en mi opinión: definir una función matemática que funcione para cualquier número racional y utilizar el hecho de que los números de coma flotante también son números racionales.
ShreevatsaR

1
@ShreevatsaR Ah, gracias. Si bien no me hubiera importado garantizar estas igualdades, es bueno saber que hay una explicación buena, sólida y lógica para el código aparentemente complejo :-)
cmaster - restablecer monica

2
@cmaster La función hash para enteros es simplemente hash(n) = n % Mdonde M = (2 ^ 61 - 1). Esto se generaliza para n to racional hash(p/q) = (p/q) mod Mcon la división que se interpreta módulo M (en otras palabras:) hash(p/q) = (p * inverse(q, M)) % M. La razón por la que queremos esto: si en un dict dponemos d[x] = fooy luego tenemos x==y(por ejemplo, 42.0 == 42) pero d[y]no es lo mismo d[x], entonces tendríamos un problema. La mayor parte del código aparentemente complejo proviene de la naturaleza del formato de punto flotante en sí mismo, para recuperar la fracción correctamente y necesita casos especiales para valores de inf y NaN.
ShreevatsaR

12

En efecto,

sys.hash_info.inf

vuelve 314159. El valor no se genera, está integrado en el código fuente. De hecho,

hash(float('-inf'))

devuelve -271828, o aproximadamente -e, en python 2 ( ahora es -314159 ).

El hecho de que se usen los dos números irracionales más famosos de todos los tiempos como valores hash hace que sea muy poco probable que sea una coincidencia.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.