diff --git a/Lib/decimal.py b/Lib/decimal.py index 52ac7a8b8be..34463aefa4b 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -935,14 +935,30 @@ class Decimal(object): # The hash of a nonspecial noninteger Decimal must depend only # on the value of that Decimal, and not on its representation. # For example: hash(Decimal('100E-1')) == hash(Decimal('10')). - if self._is_special and self._isnan(): - raise TypeError('Cannot hash a NaN value.') + + # Equality comparisons involving signaling nans can raise an + # exception; since equality checks are implicitly and + # unpredictably used when checking set and dict membership, we + # prevent signaling nans from being used as set elements or + # dict keys by making __hash__ raise an exception. + if self._is_special: + if self.is_snan(): + raise TypeError('Cannot hash a signaling NaN value.') + elif self.is_nan(): + # 0 to match hash(float('nan')) + return 0 + else: + # values chosen to match hash(float('inf')) and + # hash(float('-inf')). + if self._sign: + return -271828 + else: + return 314159 # In Python 2.7, we're allowing comparisons (but not # arithmetic operations) between floats and Decimals; so if # a Decimal instance is exactly representable as a float then - # its hash should match that of the float. Note that this takes care - # of zeros and infinities, as well as small integers. + # its hash should match that of the float. self_as_float = float(self) if Decimal.from_float(self_as_float) == self: return hash(self_as_float) diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index c2a6b0e0074..19fb9dadc43 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1274,6 +1274,10 @@ class DecimalUsabilityTest(unittest.TestCase): def test_hash_method(self): #just that it's hashable hash(Decimal(23)) + hash(Decimal('Infinity')) + hash(Decimal('-Infinity')) + hash(Decimal('nan123')) + hash(Decimal('-NaN')) test_values = [Decimal(sign*(2**m + n)) for m in [0, 14, 15, 16, 17, 30, 31, @@ -1308,7 +1312,7 @@ class DecimalUsabilityTest(unittest.TestCase): #the same hash that to an int self.assertEqual(hash(Decimal(23)), hash(23)) - self.assertRaises(TypeError, hash, Decimal('NaN')) + self.assertRaises(TypeError, hash, Decimal('sNaN')) self.assertTrue(hash(Decimal('Inf'))) self.assertTrue(hash(Decimal('-Inf'))) diff --git a/Misc/NEWS b/Misc/NEWS index e9b39b7d7de..8f361090202 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -37,7 +37,8 @@ Library - Issue #7279: Comparisons involving a Decimal signaling NaN now signal InvalidOperation instead of returning False. (Comparisons - involving a quiet NaN are unchanged.) + involving a quiet NaN are unchanged.) Also, Decimal quiet NaNs + are now hashable; Decimal signaling NaNs remain unhashable. - Issue #2531: Comparison operations between floats and Decimal instances now return a result based on the numeric values of the