Warum hat Pythons Hash der Unendlichkeit die Ziffern von π?

Lesezeit: 6 Minuten

Benutzer-Avatar
Wim

Der Hash der Unendlichkeit in Python hat übereinstimmende Ziffern Pi:

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

Ist das nur Zufall oder Absicht?

  • Nicht sicher, aber ich vermute, dass es so beabsichtigt ist wie hash(float('nan')) Sein 0.

    – cs95

    20. Mai 2019 um 20:04 Uhr

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

    – Wim

    20. Mai 2019 um 20:09 Uhr

  • Fragen Sie Tim Peters. Hier ist der Commit, in 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 überarbeitet habe bugs.python.org/issue8188

    – Mark Dickinson

    20. Mai 2019 um 20:38 Uhr


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

    – Wim

    20. Mai 2019 um 20:42 Uhr


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

    – Mark Dickinson

    20. Mai 2019 um 20:44 Uhr

Benutzer-Avatar
ShreevatsaR

Zusammenfassung: Es ist kein Zufall; _PyHASH_INF ist als 314159 fest codiert in der standardmäßigen CPython-Implementierung von Python und wurde als willkürlicher Wert ausgewählt (offensichtlich aus den Ziffern von π) von Tim Peters im Jahr 2000.


Der Wert von hash(float('inf')) ist einer der systemabhängigen Parameter der eingebauten Hash-Funktion für numerische Typen und ist ebenfalls verfügbar wie sys.hash_info.inf in Python3:

>>> 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 mit PyPy zu.)


In Bezug auf Code, hash ist eine eingebaute Funktion. Der Aufruf für ein Python-Float-Objekt ruft die Funktion auf, deren Zeiger durch die gegeben ist tp_hash Attribut vom eingebauten Schwimmertyp (PyTypeObject PyFloat_Type), die ist das float_hash Funktion, definiert wie return _Py_HashDouble(v->ob_fval)was wiederum hat

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

wo _PyHASH_INF ist definiert als 314159:

#define _PyHASH_INF 314159

In Bezug auf die Geschichte, die erste Erwähnung von 314159 in diesem Zusammenhang im Python-Code (findest du mit git bisect oder git log -S 314159 -p) wurde hinzugefügt von Tim Peters im August 2000, in dem, was jetzt begangen wird 39dce293 in dem cpython Git-Repository.

Die Commit-Nachricht sagt:

Fix für http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470. Das war ein irreführender Fehler – der wahre „Fehler“ war das hash(x) gab einen Fehler zurück, wenn x ist eine Unendlichkeit. Das wurde behoben. Neu hinzugefügt Py_IS_INFINITY Makro zu
pyport.h. Der Code wurde neu angeordnet, um die zunehmende Duplizierung beim Hashing von Gleitkommazahlen und komplexen Zahlen zu reduzieren, wodurch Trents früherer Versuch, dies zu tun, zu einer logischen Schlussfolgerung geführt wurde. Äußerst seltener Fehler behoben, bei dem das Hashing von Floats -1 zurückgeben konnte, selbst wenn kein Fehler vorlag (habe keine Zeit damit verschwendet, einen Testfall zu erstellen, es war einfach aus dem Code ersichtlich, dass es könnte passieren). Verbesserter komplexer Hash, damit
hash(complex(x, y)) nicht systematisch gleich hash(complex(y, x)) mehr.

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

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

Also, wie gesagt, es war eine willkürliche Wahl. Beachten Sie, dass 271828 aus den ersten Dezimalstellen von gebildet wird e.

Zugehörige spätere Commits:

  • Die Wahl von -271828 für -Inf beseitigt jeden Zweifel, dass die pi-Verknüpfung zufällig war.

    – Russell Borogove

    21. Mai 2019 um 4:30 Uhr

  • @RussellBorogove Nein, aber es macht es ungefähr eine Million Mal unwahrscheinlicher;)

    – Rohr

    21. Mai 2019 um 15:01 Uhr

  • @cmaster: Siehe den Teil oben, wo Mai 2010 steht, nämlich den Dokumentationsabschnitt Hashing numerischer Typen und Ausgabe 8188 – Die Idee ist, dass wir wollen hash(42.0) gleich sein wie hash(42)auch das gleiche wie hash(Decimal(42)) und hash(complex(42)) und hash(Fraction(42, 1)). Die Lösung (von Mark Dickinson) ist meines Erachtens elegant: Definieren Sie eine mathematische Funktion, die für jede rationale Zahl funktioniert, und nutzen Sie die Tatsache, dass Gleitkommazahlen auch rationale Zahlen sind.

    – ShreevatsaR

    22. Mai 2019 um 13:22 Uhr

  • @ShreevatsaR Ah, danke. Obwohl ich diese Gleichheiten nicht garantieren wollte, ist es gut zu wissen, dass es eine gute, solide und logische Erklärung für den scheinbar komplexen Code gibt 🙂

    – Cmaster – Wiedereinsetzung von Monica

    22. Mai 2019 um 14:00 Uhr

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

    – ShreevatsaR

    22. Mai 2019 um 23:22 Uhr

Benutzer-Avatar
Patrick Hagh

_PyHASH_INF ist als Konstante definiert gleicht 314159.

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

  • Kleine Spitzfindigkeit: Es ist per Definition fast unvermeidlich, dass derselbe Wert für andere Hashes verwendet wird, z. B. in diesem Fall hash(314159) ist auch 314159. Versuchen Sie auch in Python 3, hash(2305843009214008110) == 314159 (Diese Eingabe ist 314159 + sys.hash_info.modulus) etc.

    – ShreevatsaR

    21. Mai 2019 um 11:43 Uhr

  • @ShreevatsaR Ich meinte nur, dass die Auswahl eines sinnvollen Werts wie diesem die Wahrscheinlichkeit von Hash-Kollisionen nicht erhöht, solange sie diesen Wert nicht per Definition als Hash anderer Werte auswählen

    – Patrick Hagh

    21. Mai 2019 um 13:37 Uhr

Benutzer-Avatar
Alec

In der Tat,

sys.hash_info.inf

kehrt zurück 314159. Der Wert wird nicht generiert, sondern in den Quellcode eingebaut. In der Tat,

hash(float('-inf'))

kehrt zurück -271828oder ungefähr -e, in Python 2 (es ist jetzt -314159).

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

1055870cookie-checkWarum hat Pythons Hash der Unendlichkeit die Ziffern von π?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy