Issue #8188: Comparisons between Decimal objects and other numeric

objects (Fraction, float, complex, int) now all function as expected.
This commit is contained in:
Mark Dickinson 2010-06-11 10:44:52 +00:00
parent bfd73faf86
commit 08ade6faa0
5 changed files with 112 additions and 25 deletions

View File

@ -363,23 +363,17 @@ Decimal objects
compared, sorted, and coerced to another type (such as :class:`float` or
:class:`int`).
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.
Decimal objects cannot generally be combined with floats or
instances of :class:`fractions.Fraction` in arithmetic operations:
an attempt to add a :class:`Decimal` to a :class:`float`, for
example, will raise a :exc:`TypeError`. However, it is possible to
use Python's comparison operators to compare a :class:`Decimal`
instance ``x`` with another number ``y``. This avoids confusing results
when doing equality comparisons between numbers of different types.
.. versionchanged:: 3.2
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``.
Mixed-type comparisons between :class:`Decimal` instances and
other numeric types are now fully supported.
In addition to the standard numeric properties, decimal floating point
objects also have a number of specialized methods:

View File

@ -862,7 +862,7 @@ class Decimal(object):
# that specified by IEEE 754.
def __eq__(self, other, context=None):
other = _convert_other(other, allow_float = True)
self, other = _convert_for_comparison(self, other, equality_op=True)
if other is NotImplemented:
return other
if self._check_nans(other, context):
@ -870,7 +870,7 @@ class Decimal(object):
return self._cmp(other) == 0
def __ne__(self, other, context=None):
other = _convert_other(other, allow_float = True)
self, other = _convert_for_comparison(self, other, equality_op=True)
if other is NotImplemented:
return other
if self._check_nans(other, context):
@ -879,7 +879,7 @@ class Decimal(object):
def __lt__(self, other, context=None):
other = _convert_other(other, allow_float = True)
self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@ -888,7 +888,7 @@ class Decimal(object):
return self._cmp(other) < 0
def __le__(self, other, context=None):
other = _convert_other(other, allow_float = True)
self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@ -897,7 +897,7 @@ class Decimal(object):
return self._cmp(other) <= 0
def __gt__(self, other, context=None):
other = _convert_other(other, allow_float = True)
self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@ -906,7 +906,7 @@ class Decimal(object):
return self._cmp(other) > 0
def __ge__(self, other, context=None):
other = _convert_other(other, allow_float = True)
self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@ -5860,6 +5860,37 @@ def _convert_other(other, raiseit=False, allow_float=False):
raise TypeError("Unable to convert %s to Decimal" % other)
return NotImplemented
def _convert_for_comparison(self, other, equality_op=False):
"""Given a Decimal instance self and a Python object other, return
an pair (s, o) of Decimal instances such that "s op o" is
equivalent to "self op other" for any of the 6 comparison
operators "op".
"""
if isinstance(other, Decimal):
return self, other
# Comparison with a Rational instance (also includes integers):
# self op n/d <=> self*d op n (for n and d integers, d positive).
# A NaN or infinity can be left unchanged without affecting the
# comparison result.
if isinstance(other, _numbers.Rational):
if not self._is_special:
self = _dec_from_triple(self._sign,
str(int(self._int) * other.denominator),
self._exp)
return self, Decimal(other.numerator)
# Comparisons with float and complex types. == and != comparisons
# with complex numbers should succeed, returning either True or False
# as appropriate. Other comparisons return NotImplemented.
if equality_op and isinstance(other, _numbers.Complex) and other.imag == 0:
other = other.real
if isinstance(other, float):
return self, Decimal.from_float(other)
return NotImplemented, NotImplemented
##### Setup Specific Contexts ############################################
# The default context prototype used by Context()

View File

@ -395,12 +395,11 @@ class FractionTest(unittest.TestCase):
self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** F(1, 10))
def testMixingWithDecimal(self):
# Decimal refuses mixed comparisons.
# Decimal refuses mixed arithmetic (but not mixed comparisons)
self.assertRaisesMessage(
TypeError,
"unsupported operand type(s) for +: 'Fraction' and 'Decimal'",
operator.add, F(3,11), Decimal('3.1415926'))
self.assertNotEquals(F(5, 2), Decimal('2.5'))
def testComparisons(self):
self.assertTrue(F(1, 2) < F(2, 3))

View File

@ -143,9 +143,64 @@ class HashTest(unittest.TestCase):
x = {'halibut', HalibutProxy()}
self.assertEqual(len(x), 1)
class ComparisonTest(unittest.TestCase):
def test_mixed_comparisons(self):
# ordered list of distinct test values of various types:
# int, float, Fraction, Decimal
test_values = [
float('-inf'),
D('-1e999999999'),
-1e308,
F(-22, 7),
-3.14,
-2,
0.0,
1e-320,
True,
F('1.2'),
D('1.3'),
float('1.4'),
F(275807, 195025),
D('1.414213562373095048801688724'),
F(114243, 80782),
F(473596569, 84615),
7e200,
D('infinity'),
]
for i, first in enumerate(test_values):
for second in test_values[i+1:]:
self.assertLess(first, second)
self.assertLessEqual(first, second)
self.assertGreater(second, first)
self.assertGreaterEqual(second, first)
def test_complex(self):
# comparisons with complex are special: equality and inequality
# comparisons should always succeed, but order comparisons should
# raise TypeError.
z = 1.0 + 0j
w = -3.14 + 2.7j
for v in 1, 1.0, F(1), D(1), complex(1):
self.assertEqual(z, v)
self.assertEqual(v, z)
for v in 2, 2.0, F(2), D(2), complex(2):
self.assertNotEqual(z, v)
self.assertNotEqual(v, z)
self.assertNotEqual(w, v)
self.assertNotEqual(v, w)
for v in (1, 1.0, F(1), D(1), complex(1),
2, 2.0, F(2), D(2), complex(2), w):
for op in operator.le, operator.lt, operator.ge, operator.gt:
self.assertRaises(TypeError, op, z, v)
self.assertRaises(TypeError, op, v, z)
def test_main():
run_unittest(HashTest)
run_unittest(HashTest, ComparisonTest)
if __name__ == '__main__':
test_main()

View File

@ -417,6 +417,13 @@ C-API
Library
-------
- Issue #8118: Comparisons between Decimal and Fraction objects are
now permitted, returning a result based on the exact numerical
values of the operands. This builds on issue #2531, which allowed
Decimal-to-float comparisons; all comparisons involving numeric
types (bool, int, float, complex, Decimal, Fraction) should now
act as expected.
- Issue #8897: Fix sunau module, use bytes to write the header. Patch written
by Thomas Jollans.
@ -714,7 +721,8 @@ 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).
the relative ordering of id(float) and id(Decimal). See also
issue #8118, which adds Decimal-to-Fraction comparisons.
- Added a subtract() method to collections.Counter().