]> granicus.if.org Git - python/commitdiff
Issue #8748: Fix two issues with comparisons between complex and integer
authorMark Dickinson <dickinsm@gmail.com>
Fri, 21 May 2010 14:55:26 +0000 (14:55 +0000)
committerMark Dickinson <dickinsm@gmail.com>
Fri, 21 May 2010 14:55:26 +0000 (14:55 +0000)
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.

Patch by Meador Inge.

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

index 6441208d80b8b97ff89620d6eae3d660d0581ce7..f6c7cc30ac22178023f20948a2bf08ca85288c0b 100644 (file)
@@ -110,12 +110,18 @@ class ComplexTest(unittest.TestCase):
         self.assertRaises(TypeError, complex.__floordiv__, 3+0j, 0+0j)
 
     def test_richcompare(self):
-        self.assertRaises(OverflowError, complex.__eq__, 1+1j, 1<<10000)
+        self.assertIs(complex.__eq__(1+1j, 1<<10000), False)
         self.assertIs(complex.__lt__(1+1j, None), NotImplemented)
         self.assertIs(complex.__eq__(1+1j, 1+1j), True)
         self.assertIs(complex.__eq__(1+1j, 2+2j), False)
         self.assertIs(complex.__ne__(1+1j, 1+1j), False)
         self.assertIs(complex.__ne__(1+1j, 2+2j), True)
+        for i in range(1, 100):
+            f = i / 100.0
+            self.assertIs(complex.__eq__(f+0j, f), True)
+            self.assertIs(complex.__ne__(f+0j, f), False)
+            self.assertIs(complex.__eq__(complex(f, f), f), False)
+            self.assertIs(complex.__ne__(complex(f, f), f), True)
         self.assertIs(complex.__lt__(1+1j, 2+2j), NotImplemented)
         self.assertIs(complex.__le__(1+1j, 2+2j), NotImplemented)
         self.assertIs(complex.__gt__(1+1j, 2+2j), NotImplemented)
@@ -129,6 +135,23 @@ class ComplexTest(unittest.TestCase):
         self.assertIs(operator.ne(1+1j, 1+1j), False)
         self.assertIs(operator.ne(1+1j, 2+2j), True)
 
+    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):
         # % is no longer supported on complex numbers
         self.assertRaises(TypeError, (1+1j).__mod__, 0+0j)
index 5ac16dfc07598ad01db8b8583fb374bbc72aefab..9772a150e82ecaadbe760bbf0252a119cf54463b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,19 @@ What's New in Python 3.2 Alpha 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 #8748: Fix comparisons between complex and integer objects.
+  These used to convert the integer object to a complex number before
+  doing the comparison, giving a potentially incorrect result when
+  that conversion involved precision loss.  (Ex: 2**53+1 ==
+  complex(2**53) returned True; now returns False.)
+
 - Issue #8766: Initialize _warnings module before importing the first module.
   Fix a crash if an empty directory called "encodings" exists in sys.path.
 
index ec26e0a700359914c8c351debfec69ac12463932..9e1e2178561711000c927482acf5cfe61e63122f 100644 (file)
@@ -620,22 +620,58 @@ static PyObject *
 complex_richcompare(PyObject *v, PyObject *w, int op)
 {
     PyObject *res;
-    Py_complex i, j;
-    TO_COMPLEX(v, i);
-    TO_COMPLEX(w, j);
+    Py_complex i;
+    int equal;
 
     if (op != Py_EQ && op != Py_NE) {
-        Py_INCREF(Py_NotImplemented);
-        return Py_NotImplemented;
+        goto Unimplemented;
     }
 
-    if ((i.real == j.real && i.imag == j.imag) == (op == Py_EQ))
-        res = Py_True;
+    assert(PyComplex_Check(v));
+    TO_COMPLEX(v, i);
+
+    if (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 (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 *