Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 08ade6f

Browse files
committed
Issue #8188: Comparisons between Decimal objects and other numeric
objects (Fraction, float, complex, int) now all function as expected.
1 parent bfd73fa commit 08ade6f

5 files changed

Lines changed: 112 additions & 25 deletions

File tree

Doc/library/decimal.rst

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -363,23 +363,17 @@ Decimal objects
363363
compared, sorted, and coerced to another type (such as :class:`float` or
364364
:class:`int`).
365365

366-
Decimal objects cannot generally be combined with floats in
367-
arithmetic operations: an attempt to add a :class:`Decimal` to a
368-
:class:`float`, for example, will raise a :exc:`TypeError`.
369-
There's one exception to this rule: it's possible to use Python's
370-
comparison operators to compare a :class:`float` instance ``x``
371-
with a :class:`Decimal` instance ``y``. Without this exception,
372-
comparisons between :class:`Decimal` and :class:`float` instances
373-
would follow the general rules for comparing objects of different
374-
types described in the :ref:`expressions` section of the reference
375-
manual, leading to confusing results.
366+
Decimal objects cannot generally be combined with floats or
367+
instances of :class:`fractions.Fraction` in arithmetic operations:
368+
an attempt to add a :class:`Decimal` to a :class:`float`, for
369+
example, will raise a :exc:`TypeError`. However, it is possible to
370+
use Python's comparison operators to compare a :class:`Decimal`
371+
instance ``x`` with another number ``y``. This avoids confusing results
372+
when doing equality comparisons between numbers of different types.
376373

377374
.. versionchanged:: 3.2
378-
A comparison between a :class:`float` instance ``x`` and a
379-
:class:`Decimal` instance ``y`` now returns a result based on
380-
the values of ``x`` and ``y``. In earlier versions ``x < y``
381-
returned the same (arbitrary) result for any :class:`Decimal`
382-
instance ``x`` and any :class:`float` instance ``y``.
375+
Mixed-type comparisons between :class:`Decimal` instances and
376+
other numeric types are now fully supported.
383377

384378
In addition to the standard numeric properties, decimal floating point
385379
objects also have a number of specialized methods:

Lib/decimal.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -862,15 +862,15 @@ def _cmp(self, other):
862862
# that specified by IEEE 754.
863863

864864
def __eq__(self, other, context=None):
865-
other = _convert_other(other, allow_float = True)
865+
self, other = _convert_for_comparison(self, other, equality_op=True)
866866
if other is NotImplemented:
867867
return other
868868
if self._check_nans(other, context):
869869
return False
870870
return self._cmp(other) == 0
871871

872872
def __ne__(self, other, context=None):
873-
other = _convert_other(other, allow_float = True)
873+
self, other = _convert_for_comparison(self, other, equality_op=True)
874874
if other is NotImplemented:
875875
return other
876876
if self._check_nans(other, context):
@@ -879,7 +879,7 @@ def __ne__(self, other, context=None):
879879

880880

881881
def __lt__(self, other, context=None):
882-
other = _convert_other(other, allow_float = True)
882+
self, other = _convert_for_comparison(self, other)
883883
if other is NotImplemented:
884884
return other
885885
ans = self._compare_check_nans(other, context)
@@ -888,7 +888,7 @@ def __lt__(self, other, context=None):
888888
return self._cmp(other) < 0
889889

890890
def __le__(self, other, context=None):
891-
other = _convert_other(other, allow_float = True)
891+
self, other = _convert_for_comparison(self, other)
892892
if other is NotImplemented:
893893
return other
894894
ans = self._compare_check_nans(other, context)
@@ -897,7 +897,7 @@ def __le__(self, other, context=None):
897897
return self._cmp(other) <= 0
898898

899899
def __gt__(self, other, context=None):
900-
other = _convert_other(other, allow_float = True)
900+
self, other = _convert_for_comparison(self, other)
901901
if other is NotImplemented:
902902
return other
903903
ans = self._compare_check_nans(other, context)
@@ -906,7 +906,7 @@ def __gt__(self, other, context=None):
906906
return self._cmp(other) > 0
907907

908908
def __ge__(self, other, context=None):
909-
other = _convert_other(other, allow_float = True)
909+
self, other = _convert_for_comparison(self, other)
910910
if other is NotImplemented:
911911
return other
912912
ans = self._compare_check_nans(other, context)
@@ -5860,6 +5860,37 @@ def _convert_other(other, raiseit=False, allow_float=False):
58605860
raise TypeError("Unable to convert %s to Decimal" % other)
58615861
return NotImplemented
58625862

5863+
def _convert_for_comparison(self, other, equality_op=False):
5864+
"""Given a Decimal instance self and a Python object other, return
5865+
an pair (s, o) of Decimal instances such that "s op o" is
5866+
equivalent to "self op other" for any of the 6 comparison
5867+
operators "op".
5868+
5869+
"""
5870+
if isinstance(other, Decimal):
5871+
return self, other
5872+
5873+
# Comparison with a Rational instance (also includes integers):
5874+
# self op n/d <=> self*d op n (for n and d integers, d positive).
5875+
# A NaN or infinity can be left unchanged without affecting the
5876+
# comparison result.
5877+
if isinstance(other, _numbers.Rational):
5878+
if not self._is_special:
5879+
self = _dec_from_triple(self._sign,
5880+
str(int(self._int) * other.denominator),
5881+
self._exp)
5882+
return self, Decimal(other.numerator)
5883+
5884+
# Comparisons with float and complex types. == and != comparisons
5885+
# with complex numbers should succeed, returning either True or False
5886+
# as appropriate. Other comparisons return NotImplemented.
5887+
if equality_op and isinstance(other, _numbers.Complex) and other.imag == 0:
5888+
other = other.real
5889+
if isinstance(other, float):
5890+
return self, Decimal.from_float(other)
5891+
return NotImplemented, NotImplemented
5892+
5893+
58635894
##### Setup Specific Contexts ############################################
58645895

58655896
# The default context prototype used by Context()

Lib/test/test_fractions.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,11 @@ def testMixedArithmetic(self):
395395
self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** F(1, 10))
396396

397397
def testMixingWithDecimal(self):
398-
# Decimal refuses mixed comparisons.
398+
# Decimal refuses mixed arithmetic (but not mixed comparisons)
399399
self.assertRaisesMessage(
400400
TypeError,
401401
"unsupported operand type(s) for +: 'Fraction' and 'Decimal'",
402402
operator.add, F(3,11), Decimal('3.1415926'))
403-
self.assertNotEquals(F(5, 2), Decimal('2.5'))
404403

405404
def testComparisons(self):
406405
self.assertTrue(F(1, 2) < F(2, 3))

Lib/test/test_numeric_tower.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,64 @@ def __eq__(self, other):
143143
x = {'halibut', HalibutProxy()}
144144
self.assertEqual(len(x), 1)
145145

146+
class ComparisonTest(unittest.TestCase):
147+
def test_mixed_comparisons(self):
148+
149+
# ordered list of distinct test values of various types:
150+
# int, float, Fraction, Decimal
151+
test_values = [
152+
float('-inf'),
153+
D('-1e999999999'),
154+
-1e308,
155+
F(-22, 7),
156+
-3.14,
157+
-2,
158+
0.0,
159+
1e-320,
160+
True,
161+
F('1.2'),
162+
D('1.3'),
163+
float('1.4'),
164+
F(275807, 195025),
165+
D('1.414213562373095048801688724'),
166+
F(114243, 80782),
167+
F(473596569, 84615),
168+
7e200,
169+
D('infinity'),
170+
]
171+
for i, first in enumerate(test_values):
172+
for second in test_values[i+1:]:
173+
self.assertLess(first, second)
174+
self.assertLessEqual(first, second)
175+
self.assertGreater(second, first)
176+
self.assertGreaterEqual(second, first)
177+
178+
def test_complex(self):
179+
# comparisons with complex are special: equality and inequality
180+
# comparisons should always succeed, but order comparisons should
181+
# raise TypeError.
182+
z = 1.0 + 0j
183+
w = -3.14 + 2.7j
184+
185+
for v in 1, 1.0, F(1), D(1), complex(1):
186+
self.assertEqual(z, v)
187+
self.assertEqual(v, z)
188+
189+
for v in 2, 2.0, F(2), D(2), complex(2):
190+
self.assertNotEqual(z, v)
191+
self.assertNotEqual(v, z)
192+
self.assertNotEqual(w, v)
193+
self.assertNotEqual(v, w)
194+
195+
for v in (1, 1.0, F(1), D(1), complex(1),
196+
2, 2.0, F(2), D(2), complex(2), w):
197+
for op in operator.le, operator.lt, operator.ge, operator.gt:
198+
self.assertRaises(TypeError, op, z, v)
199+
self.assertRaises(TypeError, op, v, z)
200+
146201

147202
def test_main():
148-
run_unittest(HashTest)
203+
run_unittest(HashTest, ComparisonTest)
149204

150205
if __name__ == '__main__':
151206
test_main()

Misc/NEWS

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,13 @@ C-API
417417
Library
418418
-------
419419

420+
- Issue #8118: Comparisons between Decimal and Fraction objects are
421+
now permitted, returning a result based on the exact numerical
422+
values of the operands. This builds on issue #2531, which allowed
423+
Decimal-to-float comparisons; all comparisons involving numeric
424+
types (bool, int, float, complex, Decimal, Fraction) should now
425+
act as expected.
426+
420427
- Issue #8897: Fix sunau module, use bytes to write the header. Patch written
421428
by Thomas Jollans.
422429

@@ -714,7 +721,8 @@ Library
714721
- Issue #2531: Comparison operations between floats and Decimal
715722
instances now return a result based on the numeric values of the
716723
operands; previously they returned an arbitrary result based on
717-
the relative ordering of id(float) and id(Decimal).
724+
the relative ordering of id(float) and id(Decimal). See also
725+
issue #8118, which adds Decimal-to-Fraction comparisons.
718726

719727
- Added a subtract() method to collections.Counter().
720728

0 commit comments

Comments
 (0)