Merged revisions 79583,79588-79589 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk
........
r79583 | mark.dickinson | 2010-04-02 09:53:22 +0100 (Fri, 02 Apr 2010) | 7 lines
Issue #2531: Make float-to-decimal comparisons return correct results.
Float to decimal comparison operations now return a result based on
the numeric values of the operands. Decimal.__hash__ has also been
fixed so that Decimal and float values that compare equal have equal
hash value.
........
r79588 | mark.dickinson | 2010-04-02 11:17:07 +0100 (Fri, 02 Apr 2010) | 2 lines
Issue #7279: Make comparisons involving a Decimal sNaN signal InvalidOperation.
........
r79589 | mark.dickinson | 2010-04-02 11:35:12 +0100 (Fri, 02 Apr 2010) | 6 lines
Issue #7279: Make Decimal('nan') hashable. Decimal('snan') remains unhashable.
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.
........
diff --git a/Lib/decimal.py b/Lib/decimal.py
index ab38ed4..727aee2 100644
--- a/Lib/decimal.py
+++ b/Lib/decimal.py
@@ -849,8 +849,11 @@
# subject of what should happen for a comparison involving a NaN.
# We take the following approach:
#
- # == comparisons involving a NaN always return False
- # != comparisons involving a NaN always return True
+ # == comparisons involving a quiet NaN always return False
+ # != comparisons involving a quiet NaN always return True
+ # == or != comparisons involving a signaling NaN signal
+ # InvalidOperation, and return False or True as above if the
+ # InvalidOperation is not trapped.
# <, >, <= and >= comparisons involving a (quiet or signaling)
# NaN signal InvalidOperation, and return False if the
# InvalidOperation is not trapped.
@@ -858,25 +861,25 @@
# This behavior is designed to conform as closely as possible to
# that specified by IEEE 754.
- def __eq__(self, other):
- other = _convert_other(other)
+ def __eq__(self, other, context=None):
+ other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
- if self.is_nan() or other.is_nan():
+ if self._check_nans(other, context):
return False
return self._cmp(other) == 0
- def __ne__(self, other):
- other = _convert_other(other)
+ def __ne__(self, other, context=None):
+ other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
- if self.is_nan() or other.is_nan():
+ if self._check_nans(other, context):
return True
return self._cmp(other) != 0
def __lt__(self, other, context=None):
- other = _convert_other(other)
+ other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -885,7 +888,7 @@
return self._cmp(other) < 0
def __le__(self, other, context=None):
- other = _convert_other(other)
+ other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -894,7 +897,7 @@
return self._cmp(other) <= 0
def __gt__(self, other, context=None):
- other = _convert_other(other)
+ other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -903,7 +906,7 @@
return self._cmp(other) > 0
def __ge__(self, other, context=None):
- other = _convert_other(other)
+ other = _convert_other(other, allow_float=True)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -937,12 +940,34 @@
# 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')).
+
+ # 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._isnan():
- raise TypeError('Cannot hash a NaN value.')
- return hash(str(self))
- if not self:
- return 0
+ 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.
+ self_as_float = float(self)
+ if Decimal.from_float(self_as_float) == self:
+ return hash(self_as_float)
+
if self._isinteger():
op = _WorkRep(self.to_integral_value())
# to make computation feasible for Decimals with large
@@ -5780,15 +5805,21 @@
##### Helper Functions ####################################################
-def _convert_other(other, raiseit=False):
+def _convert_other(other, raiseit=False, allow_float=False):
"""Convert other to Decimal.
Verifies that it's ok to use in an implicit construction.
+ If allow_float is true, allow conversion from float; this
+ is used in the comparison methods (__eq__ and friends).
+
"""
if isinstance(other, Decimal):
return other
if isinstance(other, int):
return Decimal(other)
+ if allow_float and isinstance(other, float):
+ return Decimal.from_float(other)
+
if raiseit:
raise TypeError("Unable to convert %s to Decimal" % other)
return NotImplemented