Index: Objects/intobject.c =================================================================== --- Objects/intobject.c (revision 56970) +++ Objects/intobject.c (working copy) @@ -449,10 +449,13 @@ 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. */ + Python's long, float, complex and Decimal types are hashed. */ long x = v -> ob_ival; if (x == -1) x = -2; + /* next line for compatibility with long_hash */ + if (x == LONG_MIN) + x = LONG_MAX; return x; } Index: Objects/longobject.c =================================================================== --- Objects/longobject.c (revision 56970) +++ Objects/longobject.c (working copy) @@ -1922,13 +1922,16 @@ static long long_hash(PyLongObject *v) { - long x; + unsigned long x; + long y; Py_ssize_t i; int sign; /* This is designed so that Python ints and longs with the same value hash to the same value, otherwise comparisons of mapping keys will turn out weird */ + + /* The hash value is also designed to be periodic modulo ULONG_MAX */ i = v->ob_size; sign = 1; x = 0; @@ -1941,12 +1944,22 @@ /* Force a native long #-bits (32 or 64) circular shift */ x = ((x << SHIFT) & ~MASK) | ((x >> LONG_BIT_SHIFT) & MASK); x += v->ob_digit[i]; + /* On overflow, compensate for the wraparound (= subtraction + of 2**(bits_in_long)) by incrementing. This preserves the + value modulo ULONG_MAX = 2**(bits_in_long)-1. */ + if (x < v->ob_digit[i]) + x++; } #undef LONG_BIT_SHIFT - x = x * sign; - if (x == -1) - x = -2; - return x; + if (sign == -1) + x = ~x; + if (x == ULONG_MAX) + x = 0; + /* x is now the reduction of v modulo ULONG_MAX */ + y = (long)x; + if (y < -2) + y += 1; + return y; } Index: Lib/decimal.py =================================================================== --- Lib/decimal.py (revision 56970) +++ Lib/decimal.py (working copy) @@ -780,8 +780,13 @@ if self._isnan(): raise TypeError('Cannot hash a NaN value.') return hash(str(self)) - i = int(self) - if self == Decimal(i): + if self._isinteger(): + # reduce value modulo 2**64-1 + op = _WorkRep(self.to_integral()) + m = (1<<64)-1 + i = (op.int % m)*pow(10, op.exp, m) % m + if op.sign == 1: + i = -i return hash(i) assert self.__nonzero__() # '-0' handled by integer case return hash(str(self.normalize())) Index: Lib/test/test_hash.py =================================================================== --- Lib/test/test_hash.py (revision 56970) +++ Lib/test/test_hash.py (working copy) @@ -18,10 +18,21 @@ def test_numeric_literals(self): self.same_hash(1, 1L, 1.0, 1.0+0.0j) + self.same_hash(0, 0L, 0.0, 0.0+0.0j) + self.same_hash(-1, -1L, -1.0, -1.0+0.0j) + self.same_hash(-2, -2L, -2.0, -2.0+0.0j) def test_coerced_integers(self): self.same_hash(int(1), long(1), float(1), complex(1), int('1'), float('1.0')) + self.same_hash(int(-2**31), long(-2**31), float(-2**31)) + self.same_hash(int(1-2**31), long(1-2**31), float(1-2**31)) + self.same_hash(int(2**31-1), long(2**31-1), float(2**31-1)) + # for 64-bit platforms + self.same_hash(int(2**31), long(2**31), float(2**31)) + self.same_hash(int(-2**63), long(-2**63), float(-2**63)) + self.same_hash(int(1-2**63), long(1-2**63)) + self.same_hash(int(2**63-1), long(2**63-1)) def test_coerced_floats(self): self.same_hash(long(1.23e300), float(1.23e300))