Skip to content

Commit 4ca7c3c

Browse files
committed
Issue #8748: Fix incorrect results from comparisons between an integer
and a complex instance. Based on a patch by Meador Inge.
1 parent 4b3035d commit 4ca7c3c

3 files changed

Lines changed: 79 additions & 10 deletions

File tree

‎Lib/test/test_complex.py‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def __cmp__(self, other):
129129
self.assertTrue(a < 2.0j)
130130

131131
def test_richcompare(self):
132-
self.assertRaises(OverflowError, complex.__eq__, 1+1j, 1L<<10000)
132+
self.assertEqual(complex.__eq__(1+1j, 1L<<10000), False)
133133
self.assertEqual(complex.__lt__(1+1j, None), NotImplemented)
134134
self.assertIs(complex.__eq__(1+1j, 1+1j), True)
135135
self.assertIs(complex.__eq__(1+1j, 2+2j), False)
@@ -140,6 +140,23 @@ def test_richcompare(self):
140140
self.assertRaises(TypeError, complex.__gt__, 1+1j, 2+2j)
141141
self.assertRaises(TypeError, complex.__ge__, 1+1j, 2+2j)
142142

143+
def test_richcompare_boundaries(self):
144+
def check(n, deltas, is_equal, imag = 0.0):
145+
for delta in deltas:
146+
i = n + delta
147+
z = complex(i, imag)
148+
self.assertIs(complex.__eq__(z, i), is_equal(delta))
149+
self.assertIs(complex.__ne__(z, i), not is_equal(delta))
150+
# For IEEE-754 doubles the following should hold:
151+
# x in [2 ** (52 + i), 2 ** (53 + i + 1)] -> x mod 2 ** i == 0
152+
# where the interval is representable, of course.
153+
for i in range(1, 10):
154+
pow = 52 + i
155+
mult = 2 ** i
156+
check(2 ** pow, range(1, 101), lambda delta: delta % mult == 0)
157+
check(2 ** pow, range(1, 101), lambda delta: False, float(i))
158+
check(2 ** 53, range(-100, 0), lambda delta: True)
159+
143160
def test_mod(self):
144161
self.assertRaises(ZeroDivisionError, (1+1j).__mod__, 0+0j)
145162

‎Misc/NEWS‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ What's New in Python 2.7 Release Candidate 1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #8748: Fix two issues with comparisons between complex and integer
16+
objects. (1) The comparison could incorrectly return True in some cases
17+
(2**53+1 == complex(2**53) == 2**53), breaking transivity of equality.
18+
(2) The comparison raised an OverflowError for large integers, leading
19+
to unpredictable exceptions when combining integers and complex objects
20+
in sets or dicts.
21+
1522
- Issue #5211: Implicit coercion for the complex type is now completely
1623
removed. (Coercion for arithmetic operations was already removed in 2.7
1724
alpha 4, but coercion for rich comparisons was accidentally left in.)

‎Objects/complexobject.c‎

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -783,25 +783,70 @@ complex_coerce(PyObject **pv, PyObject **pw)
783783
static PyObject *
784784
complex_richcompare(PyObject *v, PyObject *w, int op)
785785
{
786-
Py_complex i, j;
787786
PyObject *res;
787+
Py_complex i;
788+
int equal;
788789

790+
if (op != Py_EQ && op != Py_NE) {
791+
/* for backwards compatibility, comparisons with non-numbers return
792+
* NotImplemented. Only comparisons with core numeric types raise
793+
* TypeError.
794+
*/
795+
if (PyInt_Check(w) || PyLong_Check(w) ||
796+
PyFloat_Check(w) || PyComplex_Check(w)) {
797+
PyErr_SetString(PyExc_TypeError,
798+
"no ordering relation is defined "
799+
"for complex numbers");
800+
return NULL;
801+
}
802+
goto Unimplemented;
803+
}
804+
805+
assert(PyComplex_Check(v));
789806
TO_COMPLEX(v, i);
790-
TO_COMPLEX(w, j);
791807

792-
if (op != Py_EQ && op != Py_NE) {
793-
PyErr_SetString(PyExc_TypeError,
794-
"no ordering relation is defined for complex numbers");
795-
return NULL;
808+
if (PyInt_Check(w) || PyLong_Check(w)) {
809+
/* Check for 0.0 imaginary part first to avoid the rich
810+
* comparison when possible.
811+
*/
812+
if (i.imag == 0.0) {
813+
PyObject *j, *sub_res;
814+
j = PyFloat_FromDouble(i.real);
815+
if (j == NULL)
816+
return NULL;
817+
818+
sub_res = PyObject_RichCompare(j, w, op);
819+
Py_DECREF(j);
820+
return sub_res;
821+
}
822+
else {
823+
equal = 0;
824+
}
825+
}
826+
else if (PyFloat_Check(w)) {
827+
equal = (i.real == PyFloat_AsDouble(w) && i.imag == 0.0);
828+
}
829+
else if (PyComplex_Check(w)) {
830+
Py_complex j;
831+
832+
TO_COMPLEX(w, j);
833+
equal = (i.real == j.real && i.imag == j.imag);
834+
}
835+
else {
836+
goto Unimplemented;
796837
}
797838

798-
if ((i.real == j.real && i.imag == j.imag) == (op == Py_EQ))
799-
res = Py_True;
839+
if (equal == (op == Py_EQ))
840+
res = Py_True;
800841
else
801-
res = Py_False;
842+
res = Py_False;
802843

803844
Py_INCREF(res);
804845
return res;
846+
847+
Unimplemented:
848+
Py_INCREF(Py_NotImplemented);
849+
return Py_NotImplemented;
805850
}
806851

807852
static PyObject *

0 commit comments

Comments
 (0)