https://github.com/python/cpython/commit/ebe5e216c894faf164130532a9b549bab480df33
commit: ebe5e216c894faf164130532a9b549bab480df33
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-09T17:32:23Z
summary:

[3.14] gh-143006: Fix and optimize mixed comparison of float and int 
(GH-143084) (GH-143623)

When comparing negative non-integer float and int with the same number
of bits in the integer part, __neg__() in the int subclass returning
not an int caused an assertion error.

Now the integer is no longer negated. Also, reduced the number of
temporary created Python objects.
(cherry picked from commit 66bca383bd3b12d21e879d991d77b37a4c638f88)

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-12-22-22-37-53.gh-issue-143006.ZBQwbN.rst
M Lib/test/test_float.py
M Objects/floatobject.c

diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
index 00518abcb11b46..c03b0a09f71889 100644
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -651,6 +651,24 @@ class F(float, H):
         value = F('nan')
         self.assertEqual(hash(value), object.__hash__(value))
 
+    def test_issue_gh143006(self):
+        # When comparing negative non-integer float and int with the
+        # same number of bits in the integer part, __neg__() in the
+        # int subclass returning not an int caused an assertion error.
+        class EvilInt(int):
+            def __neg__(self):
+                return ""
+
+        i = -1 << 50
+        f = float(i) - 0.5
+        i = EvilInt(i)
+        self.assertFalse(f == i)
+        self.assertTrue(f != i)
+        self.assertTrue(f < i)
+        self.assertTrue(f <= i)
+        self.assertFalse(f > i)
+        self.assertFalse(f >= i)
+
 
 @unittest.skipUnless(hasattr(float, "__getformat__"), "requires __getformat__")
 class FormatFunctionsTestCase(unittest.TestCase):
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-22-22-37-53.gh-issue-143006.ZBQwbN.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-22-22-37-53.gh-issue-143006.ZBQwbN.rst
new file mode 100644
index 00000000000000..f25620389fd5cd
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-22-22-37-53.gh-issue-143006.ZBQwbN.rst
@@ -0,0 +1,2 @@
+Fix a possible assertion error when comparing negative non-integer ``float``
+and ``int`` with the same number of bits in the integer part.
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 700b8d4cbeb9fd..4cf6d509fe281b 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -442,82 +442,67 @@ float_richcompare(PyObject *v, PyObject *w, int op)
         assert(vsign != 0); /* if vsign were 0, then since wsign is
                              * not 0, we would have taken the
                              * vsign != wsign branch at the start */
-        /* We want to work with non-negative numbers. */
-        if (vsign < 0) {
-            /* "Multiply both sides" by -1; this also swaps the
-             * comparator.
-             */
-            i = -i;
-            op = _Py_SwappedOp[op];
-        }
-        assert(i > 0.0);
         (void) frexp(i, &exponent);
         /* exponent is the # of bits in v before the radix point;
          * we know that nbits (the # of bits in w) > 48 at this point
          */
         if (exponent < nbits) {
-            i = 1.0;
-            j = 2.0;
+            j = i;
+            i = 0.0;
             goto Compare;
         }
         if (exponent > nbits) {
-            i = 2.0;
-            j = 1.0;
+            j = 0.0;
             goto Compare;
         }
         /* v and w have the same number of bits before the radix
-         * point.  Construct two ints that have the same comparison
-         * outcome.
+         * point.  Construct an int from the integer part of v and
+         * update op if necessary, so comparing two ints has the same outcome.
          */
         {
             double fracpart;
             double intpart;
             PyObject *result = NULL;
             PyObject *vv = NULL;
-            PyObject *ww = w;
 
-            if (wsign < 0) {
-                ww = PyNumber_Negative(w);
-                if (ww == NULL)
-                    goto Error;
+            fracpart = modf(i, &intpart);
+            if (fracpart != 0.0) {
+                switch (op) {
+                    /* Non-integer float never equals to an int. */
+                    case Py_EQ:
+                        Py_RETURN_FALSE;
+                    case Py_NE:
+                        Py_RETURN_TRUE;
+                    /* For non-integer float, v <= w <=> v < w.
+                     * If v > 0: trunc(v) < v < trunc(v) + 1
+                     *   v < w => trunc(v) < w
+                     *   trunc(v) < w => trunc(v) + 1 <= w => v < w
+                     * If v < 0: trunc(v) - 1 < v < trunc(v)
+                     *   v < w => trunc(v) - 1 < w => trunc(v) <= w
+                     *   trunc(v) <= w => v < w
+                     */
+                    case Py_LT:
+                    case Py_LE:
+                        op = vsign > 0 ? Py_LT : Py_LE;
+                        break;
+                    /* The same as above, but with opposite directions. */
+                    case Py_GT:
+                    case Py_GE:
+                        op = vsign > 0 ? Py_GE : Py_GT;
+                        break;
+                }
             }
-            else
-                Py_INCREF(ww);
 
-            fracpart = modf(i, &intpart);
             vv = PyLong_FromDouble(intpart);
             if (vv == NULL)
                 goto Error;
 
-            if (fracpart != 0.0) {
-                /* Shift left, and or a 1 bit into vv
-                 * to represent the lost fraction.
-                 */
-                PyObject *temp;
-
-                temp = _PyLong_Lshift(ww, 1);
-                if (temp == NULL)
-                    goto Error;
-                Py_SETREF(ww, temp);
-
-                temp = _PyLong_Lshift(vv, 1);
-                if (temp == NULL)
-                    goto Error;
-                Py_SETREF(vv, temp);
-
-                temp = PyNumber_Or(vv, _PyLong_GetOne());
-                if (temp == NULL)
-                    goto Error;
-                Py_SETREF(vv, temp);
-            }
-
-            r = PyObject_RichCompareBool(vv, ww, op);
+            r = PyObject_RichCompareBool(vv, w, op);
             if (r < 0)
                 goto Error;
             result = PyBool_FromLong(r);
          Error:
             Py_XDECREF(vv);
-            Py_XDECREF(ww);
             return result;
         }
     } /* else if (PyLong_Check(w)) */

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to