Commits

Mark Dickinson committed 3da1fd7

Fix BigFloat.__hash__ to use new Python 3 hashing algorithm.

Comments (0)

Files changed (2)

bigfloat_cython/bigfloat/core.py

 _builtin_pow = pow
 
 
+_PyHASH_MODULUS = _sys.hash_info.modulus
+_PyHASH_INF = _sys.hash_info.inf
+_PyHASH_NAN = _sys.hash_info.nan
+
+# _PyHASH_2INV is the inverse of 2 modulo the prime _PyHASH_MODULUS
+_PyHASH_2INV = _builtin_pow(2, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
+
+
 def _mpfr_get_str2(base, ndigits, op, rounding_mode):
     """
     Variant of mpfr_get_str, for internal use:  simply splits off the '-'
         return negative, digits, -precision
 
     def __hash__(self):
-        # if self is exactly representable as a float, then its hash
-        # should match that of the float.  Note that this covers the
-        # case where self == 0.
-        if self == float(self) or is_nan(self):
-            return hash(float(self))
-
-        # now we must ensure that hash(self) == hash(int(self)) in the
-        # case where self is integral.  We use the (undocumented) fact
-        # that hash(n) == hash(m) for any two nonzero integers n and m
-        # that are congruent modulo 2**64-1 and have the same sign:
-        # see the source for long_hash in Objects/longobject.c.  An
-        # alternative would be to convert an integral self to an
-        # integer and take the hash of that, but that would be
-        # painfully slow for something like BigFloat('1e1000000000').
+        if is_nan(self):
+            return _PyHASH_NAN
+        elif is_inf(self):
+            return -_PyHASH_INF if is_negative(self) else _PyHASH_INF
+        elif is_zero(self):
+            return 0
+
         negative, digits, e = _mpfr_get_str2(
             16,
             0,
             self,
             ROUND_TIES_TO_EVEN,
         )
-        e -= len(digits)
-        # The value of self is (-1)**negative * int(digits, 16) *
-        # 16**e.  Compute a strictly positive integer n such that n is
-        # congruent to abs(self) modulo 2**64-1 (e.g., in the sense
-        # that the numerator of n - abs(self) is divisible by
-        # 2**64-1).
-
+        # Find binary exponent.
+        e = 4 * (e - len(digits))
+
+        # The value of self is (-1)**negative * int(digits, 16) * 2**e.
         if e >= 0:
-            n = int(digits, 16) * _builtin_pow(16, e, 2 ** 64 - 1)
+            exp_hash = _builtin_pow(2, e, _PyHASH_MODULUS)
         else:
-            n = int(digits, 16) * _builtin_pow(2 ** 60, -e, 2 ** 64 - 1)
-        return hash(-n if negative else n)
+            exp_hash = _builtin_pow(_PyHASH_2INV, -e, _PyHASH_MODULUS)
+        hash_ = int(digits, 16) * exp_hash % _PyHASH_MODULUS
+        ans = -hash_ if negative else hash_
+        return -2 if ans == -1 else ans
 
     def __ne__(self, other):
         return not (self == other)

bigfloat_cython/bigfloat/test/test_bigfloat.py

         self.assertEqual(hash(x1), hash(x3))
 
         # check that hash values match those of floats
-        self.assertEqual(hash(BigFloat('inf')), hash(float('inf')))
-        self.assertEqual(hash(BigFloat('-inf')), hash(float('-inf')))
-        self.assertEqual(hash(BigFloat('0')), hash(float('0')))
-        self.assertEqual(hash(BigFloat('-0')), hash(float('-0')))
-        self.assertEqual(hash(BigFloat('1')), hash(float('1')))
-        self.assertEqual(hash(BigFloat('-1')), hash(float('-1')))
-        self.assertEqual(hash(BigFloat('1.625')), hash(float('1.625')))
-        self.assertEqual(hash(BigFloat.exact(1.1)), hash(1.1))
+        test_values = [
+            float('inf'),
+            float('nan'),
+            0.0,
+            1.0,
+            1.625,
+            1.1,
+            1e100,
+            1.456789123123e10,
+            1.9876543456789e-10,
+            1e-100,
+            3.1415926535,
+        ]
+        test_values += [-x for x in test_values]
+        for test_value in test_values:
+            self.assertEqual(
+                hash(BigFloat.exact(test_value)),
+                hash(test_value)
+            )
 
         # check that hash(n) matches hash(BigFloat(n)) for integers n
         for n in range(-50, 50):