Ich habe mit Pythons hash-Funktion gespielt. Bei kleinen Ganzzahlen erscheint hash(n) == n
immer. Dies gilt jedoch nicht für große Zahlen:
>>> hash(2**100) == 2**100
False
Ich bin nicht überrascht, ich verstehe, dass Hash einen endlichen Wertebereich hat. Was ist das für ein Bereich?
Ich habe versucht, mit binary search die kleinste Zahl zu finden hash(n) != n
>>> import codejamhelpers # pip install codejamhelpers
>>> help(codejamhelpers.binary_search)
Help on function binary_search in module codejamhelpers.binary_search:
binary_search(f, t)
Given an increasing function :math:`f`, find the greatest non-negative integer :math:`n` such that :math:`f(n) \le t`. If :math:`f(n) > t` for all :math:`n \ge 0`, return None.
>>> f = lambda n: int(hash(n) != n)
>>> n = codejamhelpers.binary_search(f, 0)
>>> hash(n)
2305843009213693950
>>> hash(n+1)
0
Was ist das Besondere an 2305843009213693951? Ich bemerke, dass es weniger als sys.maxsize == 9223372036854775807
ist.
Edit: Ich verwende Python 3. Ich habe die gleiche binäre Suche auf Python 2 ausgeführt und ein anderes Ergebnis erhalten 2147483648, das ich sys.maxint+1
Ich habe auch mit [hash(random.random()) for i in range(10**6)]
gespielt, um die Reichweite der Hash-Funktion zu schätzen. Das Maximum liegt konstant unter n. Vergleicht man die Min., So scheint es, als wäre der Hash von Python 3 immer positiv bewertet, während der Hash von Python 2 negative Werte annehmen kann.
Basierend auf der Python-Dokumentation in pyhash.c
file:
Bei numerischen Typen basiert der Hash einer Zahl x auf der Reduktion von x modulo der Prim
P = 2**_PyHASH_BITS - 1
. Es ist so konzipiert, dasshash(x) == hash(y)
wenn x und y numerisch gleich sind, auch wenn x und y haben unterschiedliche Typen.
Für eine 64/32-Bit-Maschine wäre die Reduktion also 2 _PyHASH_BITS - 1, aber was ist _PyHASH_BITS
?
Sie finden es in der pyhash.h
header-Datei, die für eine 64-Bit-Maschine als 61 definiert wurde (weitere Erklärungen finden Sie in der pyconfig.h
-Datei).
#if SIZEOF_VOID_P >= 8
# define _PyHASH_BITS 61
#else
# define _PyHASH_BITS 31
#endif
Zunächst einmal basiert alles auf Ihrer Plattform. In meiner 64-Bit-Linux-Plattform beträgt die Reduzierung 261-1, was 2305843009213693951
ist:
>>> 2**61 - 1
2305843009213693951
Sie können auch math.frexp
verwenden, um die Mantisse und den Exponenten von sys.maxint
abzurufen, der für eine 64-Bit-Maschine zeigt, dass max int 2 ist63:
>>> import math
>>> math.frexp(sys.maxint)
(0.5, 64)
Und Sie können den Unterschied anhand eines einfachen Tests erkennen:
>>> hash(2**62) == 2**62
True
>>> hash(2**63) == 2**63
False
Lesen Sie die vollständige Dokumentation zum Python-Hash-Algorithmus https://github.com/python/cpython/blob/master/Python/pyhash.c#L34
Wie in Kommentar erwähnt, können Sie sys.hash_info
(in Python 3.X) verwenden, um eine Struktursequenz von Parametern für die Berechnung von Hashes zu erhalten.
>>> 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)
>>>
Neben dem Modul, das ich in den vorhergehenden Zeilen beschrieben habe, können Sie den inf
-Wert auch wie folgt erhalten:
>>> hash(float('inf'))
314159
>>> sys.hash_info.inf
314159
2305843009213693951
ist 2^61 - 1
. Es ist der größte Mersenne-Prim, der in 64 Bit passt.
Wenn Sie einen Hashwert erstellen müssen, indem Sie nur den Wert mod angeben, ist eine große Mersenne-Primzahl eine gute Wahl - sie ist leicht zu berechnen und gewährleistet eine gleichmäßige Verteilung der Möglichkeiten. (Obwohl ich persönlich nie einen Hash auf diese Weise machen würde)
Es ist besonders praktisch, den Modul für Fließkommazahlen zu berechnen. Sie haben eine Exponentialkomponente, die die ganze Zahl mit 2^x
multipliziert. Seit 2^61 = 1 mod 2^61-1
müssen Sie nur noch den (exponent) mod 61
berücksichtigen.
Die Hash-Funktion gibt plain int zurück. Dies bedeutet, dass der zurückgegebene Wert größer als -sys.maxint
und niedriger als sys.maxint
ist. Wenn Sie also sys.maxint + x
übergeben, wäre -sys.maxint + (x - 2)
das Ergebnis.
hash(sys.maxint + 1) == sys.maxint + 1 # False
hash(sys.maxint + 1) == - sys.maxint -1 # True
hash(sys.maxint + sys.maxint) == -sys.maxint + sys.maxint - 2 # True
Inzwischen ist 2**200
eine n
-mal größer als sys.maxint
- meine Vermutung ist, dass hash den Bereich -sys.maxint..+sys.maxint
n-mal durchläuft, bis er auf reelle Ganzzahl in diesem Bereich stoppt, wie in den obigen Code-Snippets.
Also im Allgemeinen für alle n <= sys.maxint :
hash(sys.maxint*n) == -sys.maxint*(n%2) + 2*(n%2)*sys.maxint - n/2 - (n + 1)%2 ## True
Hinweis: Dies gilt für Python 2.
Die Implementierung für den int-Typ in cpython finden Sie hier.
Es gibt nur den Wert mit Ausnahme von -1
als -2
zurück:
static long
int_hash(PyIntObject *v)
{
/* XXX If this is changed, you also need to change the way
Python's long, float and complex types are hashed. */
long x = v -> ob_ival;
if (x == -1)
x = -2;
return x;
}