Pourquoi le hachage de Python à l'infini a-t-il les chiffres de π?


241

Le hachage de l'infini en Python a des chiffres correspondant à pi :

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

Est-ce juste une coïncidence ou est-ce intentionnel?


9
Pas certain, mais je suppose que c'est aussi délibéré hash(float('nan'))qu'être 0.
cs95

1
Hmm, aucune mention à ce sujet dans sys.hash_info. Œuf de Pâques?
wim

123
Demandez à Tim Peters. Voici le commit où il a introduit cette constante, il y a 19 ans: github.com/python/cpython/commit/… . J'ai conservé ces valeurs spéciales lorsque j'ai retravaillé le hachage numérique dans bugs.python.org/issue8188
Mark Dickinson

8
@MarkDickinson Merci. Il semble que Tim ait également utilisé les chiffres de e pour le hachage de -inf à l'origine.
wim

17
@wim Ah oui, c'est vrai. Et apparemment, j'ai changé cela en -314159. J'avais oublié ça.
Mark Dickinson

Réponses:


47

_PyHASH_INFest défini comme une constante égale à 314159.

Je ne trouve aucune discussion à ce sujet, ni aucun commentaire donnant une raison. Je pense qu'il a été choisi plus ou moins arbitrairement. J'imagine que tant qu'ils n'utilisent pas la même valeur significative pour d'autres hachages, cela ne devrait pas avoir d'importance.


6
Petit pincement: il est presque inévitable par définition que la même valeur sera utilisée pour d'autres hachages, par exemple dans ce cas hash(314159)aussi 314159. Essayez également, en Python 3, hash(2305843009214008110) == 314159(cette entrée est 314159 + sys.hash_info.modulus) etc.
ShreevatsaR

3
@ShreevatsaR Je voulais juste dire que tant qu'ils ne choisissent pas cette valeur pour être le hachage d'autres valeurs par définition, alors choisir une valeur significative comme celle-ci n'augmente pas le risque de collisions de hachage
Patrick Haugh

220

Résumé: Ce n'est pas une coïncidence; _PyHASH_INFest codé en dur comme 314159 dans l'implémentation CPython par défaut de Python, et a été choisi comme valeur arbitraire (évidemment à partir des chiffres de π) par Tim Peters en 2000 .


La valeur de hash(float('inf'))est l'un des paramètres dépendants du système de la fonction de hachage intégrée pour les types numériques, et est également disponible comme sys.hash_info.infdans 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

(Même résultats avec PyPy aussi.)


En termes de code, hashest une fonction intégrée. L'appeler sur un objet float Python appelle la fonction dont le pointeur est donné par l' tp_hashattribut du type float intégré ( PyTypeObject PyFloat_Type), qui est la float_hashfonction, définie comme return _Py_HashDouble(v->ob_fval), qui à son tour a

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

_PyHASH_INFest défini comme 314159:

#define _PyHASH_INF 314159

En termes d'histoire, la première mention 314159dans ce contexte dans le code Python (vous pouvez le trouver avec git bisectou git log -S 314159 -p) a été ajoutée par Tim Peters en août 2000, dans ce qui est maintenant commit 39dce293 dans le cpythonréférentiel git.

Le message de validation dit:

Correctif pour http://sourceforge.net/bugs/?func=detailbug&bug_id=111866&group_id=5470 . C'était un bug trompeur - le vrai "bug" était celui qui hash(x)renvoyait une erreur quand il xétait infini. Fixe ça. Ajout d'une nouvelle Py_IS_INFINITYmacro à pyport.h. Code réarrangé pour réduire la duplication croissante dans le hachage des nombres flottants et complexes, poussant Trent plus tôt à cela à une conclusion logique. Correction d'un bug extrêmement rare où le hachage des flottants pouvait retourner -1 même s'il n'y avait pas d'erreur (n'a pas perdu de temps à essayer de construire un cas de test, il était tout simplement évident d'après le code que cela pouvait arriver). Hachage complexe amélioré afin que ce hash(complex(x, y))ne soit plus systématiquement égal hash(complex(y, x)).

En particulier, dans ce commit, il a arraché le code de static long float_hash(PyFloatObject *v)in Objects/floatobject.cet l'a rendu juste return _Py_HashDouble(v->ob_fval);, et dans la définition de long _Py_HashDouble(double v)in, Objects/object.cil a ajouté les lignes:

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

Donc, comme mentionné, c'était un choix arbitraire. Notez que 271828 est formé à partir des premiers chiffres décimaux de e .

Commits ultérieurs associés:


44
Le choix de -271828 pour -Inf élimine tout doute que l'association pi était accidentelle.
Russell Borogove,

24
@RussellBorogove Non mais cela le rend environ un million de fois moins probable;)
pipe

8
@cmaster: Voir la partie ci-dessus où il est dit mai 2010, à savoir la section de documentation sur le hachage des types numériques et le problème 8188 - l'idée est que nous voulons hash(42.0)être les mêmes que hash(42), également les mêmes que hash(Decimal(42))et hash(complex(42))et hash(Fraction(42, 1)). La solution (par Mark Dickinson) est une OMI élégante: définir une fonction mathématique qui fonctionne pour n'importe quel nombre rationnel, et utiliser le fait que les nombres à virgule flottante sont aussi des nombres rationnels.
ShreevatsaR

1
@ShreevatsaR Ah, merci. Bien que je ne me serais pas soucié de garantir ces égalités, il est bon de savoir qu'il existe une explication bonne, solide et logique pour le code apparemment complexe :-)
cmaster - réintègre monica

2
@cmaster La fonction de hachage pour les entiers est simplement hash(n) = n % Moù M = (2 ^ 61 - 1). Ceci est généralisé pour rationnel n à hash(p/q) = (p/q) mod Mavec la division étant interprétée modulo M (en d'autres termes:) hash(p/q) = (p * inverse(q, M)) % M. La raison pour laquelle nous voulons ceci: si dans un dicton dnous mettons d[x] = foopuis nous avons x==y(par exemple 42.0 == 42) mais d[y]n'est pas le même que d[x], alors nous aurions un problème. La plupart du code apparemment complexe provient de la nature du format à virgule flottante lui-même, pour récupérer correctement la fraction et nécessitant des cas spéciaux pour les valeurs inf et NaN.
ShreevatsaR

12

En effet,

sys.hash_info.inf

retourne 314159. La valeur n'est pas générée, elle est intégrée au code source. En réalité,

hash(float('-inf'))

renvoie -271828, ou environ -e, en python 2 ( c'est -314159 maintenant ).

Le fait que les deux nombres irrationnels les plus célèbres de tous les temps soient utilisés comme valeurs de hachage rend très peu probable une coïncidence.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.