Warum hat Pythons Hash der Unendlichkeit die Ziffern von π?


241

Der Hash der Unendlichkeit in Python hat Ziffern, die mit pi übereinstimmen :

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

Ist das nur ein Zufall oder beabsichtigt?


9
Nicht sicher, aber ich würde vermuten, dass es so absichtlich ist wie hash(float('nan'))es ist 0.
CS95

1
Hmm, keine Erwähnung darüber in sys.hash_info. Osterei?
wim

123
Fragen Sie Tim Peters. Hier ist das Commit, bei dem er diese Konstante vor 19 Jahren eingeführt hat: github.com/python/cpython/commit/… . Ich habe diese speziellen Werte beibehalten, als ich den numerischen Hash in bugs.python.org/issue8188
Mark Dickinson

8
@ MarkDickinson Danke. Es sieht so aus, als hätte Tim ursprünglich auch die Ziffern von e für den Hash von -inf verwendet.
wim

17
@wim Ah ja, stimmt. Und anscheinend habe ich das geändert -314159. Das hatte ich vergessen.
Mark Dickinson

Antworten:


47

_PyHASH_INFist definiert als eine Konstante gleich 314159.

Ich kann keine Diskussion darüber oder Kommentare finden, die einen Grund angeben. Ich denke, es wurde mehr oder weniger willkürlich gewählt. Ich stelle mir vor, dass es keine Rolle spielen sollte, solange sie nicht den gleichen aussagekräftigen Wert für andere Hashes verwenden.


6
Kleiner nitpick: es ist fast unvermeidlich , definitionsgemäß, dass der gleiche Wert für andere Hashes verwendet werden, zB in diesem Fall hash(314159)auch 314159. Versuchen Sie auch in Python 3 hash(2305843009214008110) == 314159(diese Eingabe ist 314159 + sys.hash_info.modulus) usw.
ShreevatsaR

3
@ShreevatsaR Ich habe nur gemeint, dass, solange sie diesen Wert nicht per Definition als Hash anderer Werte auswählen, die Auswahl eines aussagekräftigen Werts wie dieser die Wahrscheinlichkeit von Hash-Kollisionen nicht erhöht
Patrick Haugh

220

Zusammenfassung: Es ist kein Zufall; _PyHASH_INFist in der Standard-CPython-Implementierung von Python als 314159 fest codiert und wurde von Tim Peters im Jahr 2000 als beliebiger Wert (offensichtlich aus den Ziffern von π) ausgewählt .


Der Wert von hash(float('inf'))ist einer der systemabhängigen Parameter der integrierten Hash-Funktion für numerische Typen und ist auch wie sys.hash_info.infin Python 3 verfügbar :

>>> 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

(Gleiche Ergebnisse auch mit PyPy .)


In Bezug auf Code hashist eine integrierte Funktion. Wenn Sie es für ein Python-Float-Objekt aufrufen, wird die Funktion aufgerufen, deren Zeiger durch das tp_hashAttribut des integrierten Float-Typs ( PyTypeObject PyFloat_Type) angegeben wird. Dies ist die float_hashFunktion, die als definiert istreturn _Py_HashDouble(v->ob_fval) und die wiederum hat

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

wo _PyHASH_INFist definiert als 314159:

#define _PyHASH_INF 314159

In Bezug auf die Geschichte wurde die erste Erwähnung 314159in diesem Zusammenhang im Python-Code (Sie finden diese mit git bisectoder git log -S 314159 -p) von Tim Peters im August 2000 in dem jetzt im Git-Repository festgeschriebenen Commit 39dce293 hinzugefügtcpython .

Die Festschreibungsnachricht lautet:

Fix für http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470 . Dies war ein irreführender Fehler - der wahre "Fehler" war, dass hash(x)ein Fehler zurückgegeben wurde, wenn xeine Unendlichkeit vorliegt . Das wurde behoben. Neues Py_IS_INFINITYMakro hinzugefügt zu pyport.h. Neu angeordneter Code, um die zunehmende Doppelarbeit beim Hashing von Float und komplexen Zahlen zu reduzieren und Trents früheren Versuch zu einer logischen Schlussfolgerung zu führen. Es wurde ein äußerst seltener Fehler behoben, bei dem das Hashing von Floats -1 zurückgeben konnte, selbst wenn kein Fehler auftrat (es wurde keine Zeit damit verschwendet, einen Testfall zu erstellen, es war einfach aus dem Code ersichtlich, dass dies passieren konnte ). Verbesserter komplexer Hash, hash(complex(x, y))der nicht hash(complex(y, x))mehr systematisch gleich ist.

Insbesondere in diesem Commit riss er den Code von static long float_hash(PyFloatObject *v)in heraus Objects/floatobject.cund machte es gerecht return _Py_HashDouble(v->ob_fval);, und in der Definition von long _Py_HashDouble(double v)in Objects/object.cfügte er die Zeilen hinzu:

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

Wie bereits erwähnt, war dies eine willkürliche Entscheidung. Beachten Sie, dass 271828 aus den ersten Dezimalstellen von e gebildet wird .

Verwandte spätere Commits:


44
Die Wahl von -271828 für -Inf beseitigt jeden Zweifel daran, dass die pi-Assoziation zufällig war.
Russell Borogove

24
@ RussellBorogove Nein, aber es macht es ungefähr eine Million Mal weniger wahrscheinlich;)
Pipe

8
@cmaster: Siehe den obigen Teil, in dem Mai 2010 steht, nämlich den Dokumentationsabschnitt zum Hashing von numerischen Typen und Ausgabe 8188 - die Idee ist, dass wir hash(42.0)dasselbe sein wollen hash(42), auch dasselbe wie hash(Decimal(42))und hash(complex(42))und hash(Fraction(42, 1)). Die Lösung (von Mark Dickinson) ist eine elegante IMO: Definieren einer mathematischen Funktion, die für jede rationale Zahl funktioniert, und Verwenden der Tatsache, dass Gleitkommazahlen auch rationale Zahlen sind.
ShreevatsaR

1
@ShreevatsaR Ah, danke. Obwohl ich mich nicht darum gekümmert hätte, diese Gleichheit zu garantieren, ist es gut zu wissen, dass es eine gute, solide und logische Erklärung für den scheinbar komplexen Code gibt :-)
cmaster - Monica

2
@cmaster Die Hash-Funktion für ganze Zahlen ist einfach, hash(n) = n % Mwo M = (2 ^ 61 - 1). Dies wird für rationales n verallgemeinert, hash(p/q) = (p/q) mod Mwobei die Division modulo M interpretiert wird (mit anderen Worten :) hash(p/q) = (p * inverse(q, M)) % M. Der Grund, warum wir das wollen: Wenn dwir in ein Diktat setzen d[x] = foound dann haben wir x==y(zB 42.0 == 42), aber d[y]nicht dasselbe wie d[x], dann hätten wir ein Problem. Der größte Teil des scheinbar komplexen Codes stammt aus der Natur des Gleitkommaformats selbst, um den Bruch ordnungsgemäß wiederherzustellen und Sonderfälle für inf- und NaN-Werte zu benötigen.
ShreevatsaR

12

Tatsächlich,

sys.hash_info.inf

kehrt zurück 314159. Der Wert wird nicht generiert, sondern in den Quellcode integriert. Eigentlich,

hash(float('-inf'))

Erträge -271828oder etwa -e, in Python 2 ( es -314.159 jetzt ist ).

Die Tatsache, dass die beiden bekanntesten irrationalen Zahlen aller Zeiten als Hash-Werte verwendet werden, macht es sehr unwahrscheinlich, dass es sich um einen Zufall handelt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.