]> granicus.if.org Git - python/commitdiff
Issue #8748: Fix incorrect results from comparisons between an integer
authorMark Dickinson <dickinsm@gmail.com>
Sun, 30 May 2010 13:18:10 +0000 (13:18 +0000)
committerMark Dickinson <dickinsm@gmail.com>
Sun, 30 May 2010 13:18:10 +0000 (13:18 +0000)
and a complex instance.  Based on a patch by Meador Inge.

Lib/test/test_complex.py
Misc/NEWS
Objects/complexobject.c

index f27593e1537bba86bf5b93681b99c2fb711a3f5c..69e27151304809258e33012244a71d7d8737887f 100644 (file)
@@ -129,7 +129,7 @@ class ComplexTest(unittest.TestCase):
         self.assertTrue(a < 2.0j)
 
     def test_richcompare(self):
-        self.assertRaises(OverflowError, complex.__eq__, 1+1j, 1L<<10000)
+        self.assertEqual(complex.__eq__(1+1j, 1L<<10000), False)
         self.assertEqual(complex.__lt__(1+1j, None), NotImplemented)
         self.assertIs(complex.__eq__(1+1j, 1+1j), True)
         self.assertIs(complex.__eq__(1+1j, 2+2j), False)
@@ -140,6 +140,23 @@ class ComplexTest(unittest.TestCase):
         self.assertRaises(TypeError, complex.__gt__, 1+1j, 2+2j)
         self.assertRaises(TypeError, complex.__ge__, 1+1j, 2+2j)
 
+    def test_richcompare_boundaries(self):
+        def check(n, deltas, is_equal, imag = 0.0):
+            for delta in deltas:
+                i = n + delta
+                z = complex(i, imag)
+                self.assertIs(complex.__eq__(z, i), is_equal(delta))
+                self.assertIs(complex.__ne__(z, i), not is_equal(delta))
+        # For IEEE-754 doubles the following should hold:
+        #    x in [2 ** (52 + i), 2 ** (53 + i + 1)] -> x mod 2 ** i == 0
+        # where the interval is representable, of course.
+        for i in range(1, 10):
+            pow = 52 + i
+            mult = 2 ** i
+            check(2 ** pow, range(1, 101), lambda delta: delta % mult == 0)
+            check(2 ** pow, range(1, 101), lambda delta: False, float(i))
+        check(2 ** 53, range(-100, 0), lambda delta: True)
+
     def test_mod(self):
         self.assertRaises(ZeroDivisionError, (1+1j).__mod__, 0+0j)
 
index e63d905d3439aaf325fbbd81acdeba47bdec7910..352ebf4e9ecf6d0ff100bc5869fcc98d56134cbd 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,13 @@ What's New in Python 2.7 Release Candidate 1?
 Core and Builtins
 -----------------
 
+- Issue #8748: Fix two issues with comparisons between complex and integer
+  objects.  (1) The comparison could incorrectly return True in some cases
+  (2**53+1 == complex(2**53) == 2**53), breaking transivity of equality.
+  (2) The comparison raised an OverflowError for large integers, leading
+  to unpredictable exceptions when combining integers and complex objects
+  in sets or dicts.
+
 - Issue #5211: Implicit coercion for the complex type is now completely
   removed.  (Coercion for arithmetic operations was already removed in 2.7
   alpha 4, but coercion for rich comparisons was accidentally left in.)
index dacafe69b67b9d9f3e175031df9ccf321cc229ea..677ac0ebe5a31191e4a92c2662d2691685a395f2 100644 (file)
@@ -783,25 +783,70 @@ complex_coerce(PyObject **pv, PyObject **pw)
 static PyObject *
 complex_richcompare(PyObject *v, PyObject *w, int op)
 {
-    Py_complex i, j;
     PyObject *res;
+    Py_complex i;
+    int equal;
 
+    if (op != Py_EQ && op != Py_NE) {
+        /* for backwards compatibility, comparisons with non-numbers return
+         * NotImplemented.  Only comparisons with core numeric types raise
+         * TypeError.
+         */
+        if (PyInt_Check(w) || PyLong_Check(w) ||
+            PyFloat_Check(w) || PyComplex_Check(w)) {
+            PyErr_SetString(PyExc_TypeError,
+                            "no ordering relation is defined "
+                            "for complex numbers");
+            return NULL;
+        }
+        goto Unimplemented;
+    }
+
+    assert(PyComplex_Check(v));
     TO_COMPLEX(v, i);
-    TO_COMPLEX(w, j);
 
-    if (op != Py_EQ && op != Py_NE) {
-        PyErr_SetString(PyExc_TypeError,
-            "no ordering relation is defined for complex numbers");
-        return NULL;
+    if (PyInt_Check(w) || PyLong_Check(w)) {
+        /* Check for 0.0 imaginary part first to avoid the rich
+         * comparison when possible.
+         */
+        if (i.imag == 0.0) {
+            PyObject *j, *sub_res;
+            j = PyFloat_FromDouble(i.real);
+            if (j == NULL)
+                return NULL;
+
+            sub_res = PyObject_RichCompare(j, w, op);
+            Py_DECREF(j);
+            return sub_res;
+        }
+        else {
+            equal = 0;
+        }
+    }
+    else if (PyFloat_Check(w)) {
+        equal = (i.real == PyFloat_AsDouble(w) && i.imag == 0.0);
+    }
+    else if (PyComplex_Check(w)) {
+        Py_complex j;
+
+        TO_COMPLEX(w, j);
+        equal = (i.real == j.real && i.imag == j.imag);
+    }
+    else {
+        goto Unimplemented;
     }
 
-    if ((i.real == j.real && i.imag == j.imag) == (op == Py_EQ))
-        res = Py_True;
+    if (equal == (op == Py_EQ))
+         res = Py_True;
     else
-        res = Py_False;
+         res = Py_False;
 
     Py_INCREF(res);
     return res;
+
+  Unimplemented:
+    Py_INCREF(Py_NotImplemented);
+    return Py_NotImplemented;
 }
 
 static PyObject *