Compare complex with int exactly instead of through a float (#9180)

complex.__eq__ and __ne__ converted an int operand to f64 before
comparing, so a complex whose real part is the float nearest to a large
int compared equal to that int even when the values differ:
complex(2**53 + 1) == 2**53 + 1 returned True, while
float(2**53 + 1) == 2**53 + 1 returns False and the two objects hash
differently.

When the imaginary part is zero, compare the real part against the
exact integer with float_ops::eq_int, the helper float's own comparison
uses. This matches CPython, which hands an int operand to float's rich
comparison. test_complex.test_richcompare_boundaries now passes, so drop
its expectedFailure marker.

Assisted-by: Claude Code:claude-opus-5-5
This commit is contained in:
Luan Taraschi authored and GitHub committed 2026-10-18 11:01:54 +09:00
1 parent cf6ca56789
commit 9fd2ba8367
2 files changed
+12 -10

No files matched your search

-1
View File
@@ -244,7 +244,6 @@ class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
self.assertIs(operator.ne(1+1j, 2+2j), True)
self.assertIs(operator.eq(1+1j, 2.0), False)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: True is not False
def test_richcompare_boundaries(self):
def check(n, deltas, is_equal, imag = 0.0):
for delta in deltas:
+12 -9
View File
@@ -1,9 +1,9 @@
use super::{PyStr, PyType, PyTypeRef, float};
use super::{PyInt, PyStr, PyType, PyTypeRef, float};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::PyUtf8StrRef,
class::{PyClassDef, PyClassImpl},
common::wtf8::Wtf8Buf,
common::{float_ops, wtf8::Wtf8Buf},
convert::{ToPyObject, ToPyResult},
function::{FuncArgs, OptionalArg, PyComparisonValue},
protocol::PyNumberMethods,
@@ -623,17 +623,20 @@ impl Comparable for PyComplex {
zelf: &Py<Self>,
other: &PyObject,
op: PyComparisonOp,
vm: &VirtualMachine,
_vm: &VirtualMachine,
) -> PyResult<PyComparisonValue> {
op.eq_only(|| {
let value = zelf.as_complex();
let result = if let Some(other) = other.downcast_ref::<Self>() {
zelf.as_complex() == other.as_complex()
value == other.as_complex()
} else if let Some(other) = other.downcast_ref::<PyInt>() {
// Compare with the exact integer, as float does, instead of
// rounding the integer to the nearest float first.
value.im == 0.0 && float_ops::eq_int(value.re, other.as_bigint())
} else if let Some(other) = other.downcast_ref::<float::PyFloat>() {
value == other.to_f64().into()
} else {
match float::to_op_float(other, vm) {
Ok(Some(other)) => zelf.as_complex() == other.into(),
Err(_) => false,
Ok(None) => return Ok(PyComparisonValue::NotImplemented),
}
return Ok(PyComparisonValue::NotImplemented);
};
Ok(PyComparisonValue::Implemented(result))
})