Perché l'hash dell'infinito di Python ha le cifre di π?


241

L'hash dell'infinito in Python ha cifre corrispondenti a pi :

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

È solo una coincidenza o è intenzionale?


9
Non sono sicuro, ma la mia ipotesi sarebbe che sia deliberato come hash(float('nan'))essere 0.
cs95,

1
Hmm, nessuna menzione al riguardo sys.hash_info. Uovo di Pasqua?
mercoledì

123
Chiedi a Tim Peters. Ecco l'impegno in cui ha introdotto questa costante, 19 anni fa: github.com/python/cpython/commit/… . Ho mantenuto quei valori speciali quando ho rielaborato l'hash numerico in bugs.python.org/issue8188
Mark Dickinson,

8
@MarkDickinson Grazie. Sembra che Tim possa aver usato anche le cifre di e per l'hash di -inf in origine.
mercoledì

17
@wim Ah sì, vero. E a quanto pare l'ho cambiato in -314159. Me ne ero dimenticato.
Mark Dickinson,

Risposte:


47

_PyHASH_INFè definito come una costante uguale a 314159.

Non riesco a trovare alcuna discussione al riguardo o commenti che forniscano una ragione. Penso che sia stato scelto più o meno arbitrariamente. Immagino che fintanto che non usano lo stesso valore significativo per altri hash, non dovrebbe importare.


6
Piccola nitpick: è quasi inevitabile per definizione che lo stesso valore verrà utilizzato per altri hash, ad esempio in questo caso hash(314159)è anche 314159. Prova anche, in Python 3, hash(2305843009214008110) == 314159(questo input è 314159 + sys.hash_info.modulus) ecc.
ShreevatsaR

3
@ShreevatsaR Volevo solo dire che fintanto che non scelgono questo valore come hash di altri valori per definizione, quindi la scelta di un valore significativo come questo non aumenta la possibilità di collisioni tra hash
Patrick Haugh,

220

Riepilogo: non è una coincidenza; _PyHASH_INFè codificato come 314159 nell'implementazione CPython predefinita di Python, ed è stato scelto come valore arbitrario (ovviamente dalle cifre di π) da Tim Peters nel 2000 .


Il valore di hash(float('inf'))è uno dei parametri dipendenti dal sistema della funzione hash integrata per i tipi numerici ed è disponibile anche come sys.hash_info.infin 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

(Stessi risultati anche con PyPy .)


In termini di codice, hashè una funzione integrata. Chiamando su un oggetto galleggiante Python invoca la funzione cui puntatore è dato dalla tp_hashattributo del tipo galleggiante incorporato ( PyTypeObject PyFloat_Type), che è la float_hashfunzione, definita come return _Py_HashDouble(v->ob_fval), che a sua volta ha

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

dove _PyHASH_INFè definito come 314159:

#define _PyHASH_INF 314159

In termini di cronologia, la prima menzione 314159in questo contesto nel codice Python (è possibile trovarlo con git bisecto git log -S 314159 -p) è stata aggiunta da Tim Peters nell'agosto 2000, in quello che ora è impegnato 39dce293 nel cpythonrepository git.

Il messaggio di commit dice:

Correzione per http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470 . Questo era un bug fuorviante - il vero "bug" era che hash(x)restituiva un errore quando era xun infinito. Risolto questo. Aggiunta nuova Py_IS_INFINITYmacro a pyport.h. Codice riorganizzato per ridurre la duplicazione crescente nell'hash di numeri mobili e complessi, spingendo la precedente pugnalata di Trent a una conclusione logica. Risolto bug estremamente raro in cui l'hashing dei float poteva restituire -1 anche se non ci fosse stato un errore (non perdere tempo nel tentativo di costruire un caso di test, era semplicemente ovvio dal codice che poteva accadere). Hash complesso migliorato in modo che hash(complex(x, y))non sia più sistematicamente uguale hash(complex(y, x)).

In particolare, in questo commit ha strappato il codice di static long float_hash(PyFloatObject *v)in Objects/floatobject.ce lo ha reso giusto return _Py_HashDouble(v->ob_fval);, e nella definizione di long _Py_HashDouble(double v)in Objects/object.cha aggiunto le righe:

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

Quindi, come detto, è stata una scelta arbitraria. Si noti che 271828 è formato dalle prime cifre decimali di e .

Impegni correlati correlati:


44
La scelta di -271828 per -Inf elimina ogni dubbio che l'associazione pi sia stata accidentale.
Russell Borogove,

24
@RussellBorogove No ma lo rende circa un milione di volte meno probabile;)
pipe

8
@cmaster: vedere la parte superiore in cui si dice maggio 2010, vale a dire la sezione relativa alla documentazione sul hashing di tipi numerici e numero 8188 - l'idea è che vogliamo hash(42.0)essere la stessa hash(42), anche lo stesso hash(Decimal(42))ed hash(complex(42))e hash(Fraction(42, 1)). La soluzione (di Mark Dickinson) è elegante come IMO: definire una funzione matematica che funziona per qualsiasi numero razionale e usare il fatto che anche i numeri in virgola mobile sono numeri razionali.
ShreevatsaR,

1
@ShreevatsaR Ah, grazie. Anche se non mi sarei preoccupato di garantire queste uguaglianze, è bene sapere che esiste una spiegazione buona, solida e logica per il codice apparentemente complesso :-)
cmaster - reintegrare monica

2
@cmaster La funzione hash per gli interi è semplicemente hash(n) = n % Mdove M = (2 ^ 61 - 1). Questo è generalizzato per n razionale hash(p/q) = (p/q) mod Mcon la divisione interpretata modulo M (in altre parole:) hash(p/q) = (p * inverse(q, M)) % M. La ragione per cui vogliamo questo: se in un dict dmettiamo d[x] = fooe poi abbiamo x==y(es. 42.0 == 42) ma d[y]non è lo stesso di d[x], allora avremmo un problema. La maggior parte del codice apparentemente complesso deriva dalla natura del formato a virgola mobile stesso, per recuperare correttamente la frazione e necessitando di casi speciali per i valori inf e NaN.
ShreevatsaR,

12

Infatti,

sys.hash_info.inf

ritorna 314159. Il valore non viene generato, è incorporato nel codice sorgente. Infatti,

hash(float('-inf'))

ritorna -271828, o approssimativamente -e, in python 2 ( ora è -314159 ).

Il fatto che i due numeri irrazionali più famosi di tutti i tempi siano utilizzati come valori di hash rende molto improbabile che si tratti di una coincidenza.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.