Skip to content

Commit

Permalink
Issue #7279: Make Decimal('nan') hashable. Decimal('snan') remains un…
Browse files Browse the repository at this point in the history
…hashable.

Also rewrite the Decimal __hash__ method so that it doesn't rely on
float('inf') being valid: float('inf') could raise an exception on
platforms not using IEEE 754 arithmetic.
  • Loading branch information
mdickinson committed Apr 2, 2010
1 parent e096e82 commit f3eeca1
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 6 deletions.
24 changes: 20 additions & 4 deletions Lib/decimal.py
Expand Up @@ -935,14 +935,30 @@ def __hash__(self):
# 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)
Expand Down
6 changes: 5 additions & 1 deletion Lib/test/test_decimal.py
Expand Up @@ -1274,6 +1274,10 @@ def test_copy_and_deepcopy_methods(self):
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,
Expand Down Expand Up @@ -1308,7 +1312,7 @@ def test_hash_method(self):

#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')))

Expand Down
3 changes: 2 additions & 1 deletion Misc/NEWS
Expand Up @@ -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
Expand Down

0 comments on commit f3eeca1

Please sign in to comment.