Por que o hash do infinito do Python tem os dígitos de π?


241

O hash do infinito no Python tem dígitos correspondentes ao pi :

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

Isso é apenas uma coincidência ou é intencional?


9
Não tenho certeza, mas meu palpite seria que é tão deliberado quanto hash(float('nan'))é 0.
cs95 20/05/19

1
Hmm, nenhuma menção a isso sys.hash_info. Ovos de pascoa?
wim

123
Pergunte a Tim Peters. Aqui está o commit em que ele introduziu essa constante, há 19 anos: github.com/python/cpython/commit/… . Eu mantive esses valores especiais quando eu reescrevi o hash numérico em bugs.python.org/issue8188
Mark Dickinson

8
@MarkDickinson Thanks. Parece que Tim também pode ter usado os dígitos de e para hash de -inf originalmente.
wim

17
@ wim Ah sim, é verdade. E aparentemente mudei isso para -314159. Eu tinha esquecido disso.
Mark Dickinson

Respostas:


47

_PyHASH_INFé definido como uma constante igual a 314159.

Não consigo encontrar nenhuma discussão sobre isso ou comentários que justifiquem. Eu acho que foi escolhido mais ou menos arbitrariamente. Eu imagino que, desde que eles não usem o mesmo valor significativo para outros hashes, isso não deve importar.


6
Pequeno nitpick: é quase inevitável por definição que o mesmo valor será usado para outros hashes, por exemplo, neste caso hash(314159)também 314159. Tente também, no Python 3, hash(2305843009214008110) == 314159(essa entrada é 314159 + sys.hash_info.modulus) etc.
ShreevatsaR

3
@ShreevatsaR Eu só quis dizer que, enquanto eles não escolher esse valor para ser o hash de outros valores por definição, então a escolha de um valor significativo como este não aumenta a chance de colisões de hash
Patrick Haugh

220

Resumo: não é uma coincidência; _PyHASH_INFé codificado como 314159 na implementação CPython padrão do Python e foi escolhido como um valor arbitrário (obviamente dos dígitos de π) por Tim Peters em 2000 .


O valor de hash(float('inf'))é um dos parâmetros dependentes do sistema da função de hash interna para tipos numéricos e também está disponível como sys.hash_info.infno 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

(Mesmos resultados com o PyPy também.)


Em termos de código, hashé uma função interna. A chamada para um objeto flutuante Python chama a função cujo ponteiro é fornecido pelo tp_hashatributo do tipo flutuante interno ( PyTypeObject PyFloat_Type), que é a float_hashfunção definida como return _Py_HashDouble(v->ob_fval), que por sua vez possui

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

onde _PyHASH_INFé definido como 314159:

#define _PyHASH_INF 314159

Em termos de histórico, a primeira menção 314159nesse contexto no código Python (você pode encontrar isso com git bisectou git log -S 314159 -p) foi adicionada por Tim Peters em agosto de 2000, no que agora é commit 39dce293 no cpythonrepositório git.

A mensagem de confirmação diz:

Correção para http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470 . Este foi um erro enganoso - o verdadeiro "erro" foi o que hash(x)deu um retorno de erro quando xé um infinito. Corrigido isso. Adicionada nova Py_IS_INFINITYmacro a pyport.h. Código reorganizado para reduzir a duplicação crescente no hash de números flutuantes e complexos, levando a facada anterior de Trent a uma conclusão lógica. Corrigido um erro extremamente raro em que o hash de flutuadores podia retornar -1, mesmo que não houvesse um erro (não perdia tempo tentando construir um caso de teste, era óbvio pelo código que isso poderia acontecer). Hash complexo aprimorado para que hash(complex(x, y))não seja hash(complex(y, x))mais igual sistematicamente .

Em particular, nesse commit, ele rasgou o código de static long float_hash(PyFloatObject *v)in Objects/floatobject.ce o fez apenas return _Py_HashDouble(v->ob_fval);, e na definição de long _Py_HashDouble(double v)in Objects/object.cele adicionou as linhas:

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

Então, como mencionado, foi uma escolha arbitrária. Observe que 271828 é formado a partir dos primeiros dígitos decimais de e .

Commits posteriores relacionados:


44
A escolha de -271828 para -Inf elimina qualquer dúvida de que a associação pi foi acidental.
Russell Borogove

24
@RussellBorogove Não, mas torna-se cerca de um milhão de vezes menos provável;)
tubo de

8
@cmaster: Veja a parte acima onde diz maio de 2010, ou seja, a seção de documentação sobre hashing de tipos numéricos e edição 8188 - a idéia é que nós queremos hash(42.0)ser o mesmo que hash(42), também o mesmo que hash(Decimal(42))e hash(complex(42))e hash(Fraction(42, 1)). A solução (de Mark Dickinson) é uma IMO elegante: definir uma função matemática que funcione para qualquer número racional e usar o fato de que números de ponto flutuante também são números racionais.
ShreevatsaR

1
@ShreevatsaR Ah, obrigado. Embora eu não teria me importado para garantir estas igualdades, é bom saber que há uma boa, sólida e explicação lógica para o código aparentemente complexo :-)
cmaster - Reintegrar monica

2
@cmaster A função hash para números inteiros é simplesmente hash(n) = n % Monde M = (2 ^ 61 - 1). Isso é generalizado para n racional hash(p/q) = (p/q) mod Mcom a divisão sendo interpretada no módulo M (em outras palavras hash(p/q) = (p * inverse(q, M)) % M:). A razão pela qual queremos isso: se dcolocarmos em um ditado d[x] = fooe depois o tivermos x==y(por exemplo, 42.0 == 42), mas d[y]não for o mesmo d[x], teríamos um problema. A maior parte do código aparentemente complexo vem da natureza do próprio formato de ponto flutuante, para recuperar a fração corretamente e necessitando de casos especiais para valores inf e NaN.
ShreevatsaR

12

De fato,

sys.hash_info.inf

retorna 314159. O valor não é gerado, é incorporado ao código fonte. De fato,

hash(float('-inf'))

retorna -271828, ou aproximadamente -e, no python 2 ( agora é -314159 ).

O fato de os dois números irracionais mais famosos de todos os tempos serem usados ​​como valores de hash torna muito improvável que seja uma coincidência.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.