mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									6eba779235
								
							
						
					
					
						commit
						99d8096c17
					
				
					 4 changed files with 74 additions and 13 deletions
				
			
		|  | @ -364,6 +364,24 @@ Decimal objects | |||
|    compared, sorted, and coerced to another type (such as :class:`float` or | ||||
|    :class:`long`). | ||||
| 
 | ||||
|    Decimal objects cannot generally be combined with floats in | ||||
|    arithmetic operations: an attempt to add a :class:`Decimal` to a | ||||
|    :class:`float`, for example, will raise a :exc:`TypeError`. | ||||
|    There's one exception to this rule: it's possible to use Python's | ||||
|    comparison operators to compare a :class:`float` instance ``x`` | ||||
|    with a :class:`Decimal` instance ``y``.  Without this exception, | ||||
|    comparisons between :class:`Decimal` and :class:`float` instances | ||||
|    would follow the general rules for comparing objects of different | ||||
|    types described in the :ref:`expressions` section of the reference | ||||
|    manual, leading to confusing results. | ||||
| 
 | ||||
|    .. versionchanged:: 2.7 | ||||
|       A comparison between a :class:`float` instance ``x`` and a | ||||
|       :class:`Decimal` instance ``y`` now returns a result based on | ||||
|       the values of ``x`` and ``y``.  In earlier versions ``x < y`` | ||||
|       returned the same (arbitrary) result for any :class:`Decimal` | ||||
|       instance ``x`` and any :class:`float` instance ``y``. | ||||
| 
 | ||||
|    In addition to the standard numeric properties, decimal floating point | ||||
|    objects also have a number of specialized methods: | ||||
| 
 | ||||
|  |  | |||
|  | @ -855,7 +855,7 @@ def _cmp(self, other): | |||
|     # that specified by IEEE 754. | ||||
| 
 | ||||
|     def __eq__(self, other): | ||||
|         other = _convert_other(other) | ||||
|         other = _convert_other(other, allow_float=True) | ||||
|         if other is NotImplemented: | ||||
|             return other | ||||
|         if self.is_nan() or other.is_nan(): | ||||
|  | @ -863,7 +863,7 @@ def __eq__(self, other): | |||
|         return self._cmp(other) == 0 | ||||
| 
 | ||||
|     def __ne__(self, other): | ||||
|         other = _convert_other(other) | ||||
|         other = _convert_other(other, allow_float=True) | ||||
|         if other is NotImplemented: | ||||
|             return other | ||||
|         if self.is_nan() or other.is_nan(): | ||||
|  | @ -871,7 +871,7 @@ def __ne__(self, other): | |||
|         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) | ||||
|  | @ -880,7 +880,7 @@ def __lt__(self, other, context=None): | |||
|         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) | ||||
|  | @ -889,7 +889,7 @@ def __le__(self, other, context=None): | |||
|         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) | ||||
|  | @ -898,7 +898,7 @@ def __gt__(self, other, context=None): | |||
|         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) | ||||
|  | @ -932,12 +932,18 @@ 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: | ||||
|             if self._isnan(): | ||||
|         if self._is_special and self._isnan(): | ||||
|             raise TypeError('Cannot hash a NaN value.') | ||||
|             return hash(str(self)) | ||||
|         if not self: | ||||
|             return 0 | ||||
| 
 | ||||
|         # 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. | ||||
|         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 | ||||
|  | @ -5695,15 +5701,21 @@ def _log10_lb(c, correction = { | |||
| 
 | ||||
| ##### 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, long)): | ||||
|         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 | ||||
|  |  | |||
|  | @ -1208,6 +1208,23 @@ def test_comparison_operators(self): | |||
|             self.assertFalse(Decimal(1) < None) | ||||
|             self.assertTrue(Decimal(1) > None) | ||||
| 
 | ||||
|     def test_decimal_float_comparison(self): | ||||
|         da = Decimal('0.25') | ||||
|         db = Decimal('3.0') | ||||
|         self.assert_(da < 3.0) | ||||
|         self.assert_(da <= 3.0) | ||||
|         self.assert_(db > 0.25) | ||||
|         self.assert_(db >= 0.25) | ||||
|         self.assert_(da != 1.5) | ||||
|         self.assert_(da == 0.25) | ||||
|         self.assert_(3.0 > da) | ||||
|         self.assert_(3.0 >= da) | ||||
|         self.assert_(0.25 < db) | ||||
|         self.assert_(0.25 <= db) | ||||
|         self.assert_(0.25 != db) | ||||
|         self.assert_(3.0 == db) | ||||
|         self.assert_(0.1 != Decimal('0.1')) | ||||
| 
 | ||||
|     def test_copy_and_deepcopy_methods(self): | ||||
|         d = Decimal('43.24') | ||||
|         c = copy.copy(d) | ||||
|  | @ -1256,6 +1273,15 @@ def test_hash_method(self): | |||
|         self.assertTrue(hash(Decimal('Inf'))) | ||||
|         self.assertTrue(hash(Decimal('-Inf'))) | ||||
| 
 | ||||
|         # check that the hashes of a Decimal float match when they | ||||
|         # represent exactly the same values | ||||
|         test_strings = ['inf', '-Inf', '0.0', '-.0e1', | ||||
|                         '34.0', '2.5', '112390.625', '-0.515625'] | ||||
|         for s in test_strings: | ||||
|             f = float(s) | ||||
|             d = Decimal(s) | ||||
|             self.assertEqual(hash(f), hash(d)) | ||||
| 
 | ||||
|         # check that the value of the hash doesn't depend on the | ||||
|         # current context (issue #1757) | ||||
|         c = getcontext() | ||||
|  |  | |||
|  | @ -35,6 +35,11 @@ Core and Builtins | |||
| Library | ||||
| ------- | ||||
| 
 | ||||
| - Issue #2531: Comparison operations between floats and Decimal | ||||
|   instances now return a result based on the numeric values of the | ||||
|   operands;  previously they returned an arbitrary result based on | ||||
|   the relative ordering of id(float) and id(Decimal). | ||||
| 
 | ||||
| - Issue #8233: When run as a script, py_compile.py optionally takes a single | ||||
|   argument `-` which tells it to read files to compile from stdin.  Each line | ||||
|   is read on demand and the named file is compiled immediately.  (Original | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Mark Dickinson
						Mark Dickinson