diff --git a/Cargo.lock b/Cargo.lock index e7d738c7c55..d00cb766efb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2081,8 +2081,7 @@ dependencies = [ [[package]] name = "num-complex" version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +source = "git+https://github.com/moreal/num-complex?rev=e179d6f7d4045ad9381249afb849fcb48831616c#e179d6f7d4045ad9381249afb849fcb48831616c" dependencies = [ "num-traits", ] diff --git a/Cargo.toml b/Cargo.toml index c62ddaf9e6b..93770a9b8e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,6 +102,7 @@ lto = "thin" [patch.crates-io] parking_lot_core = { git = "https://github.com/youknowone/parking_lot", branch = "rustpython" } +num-complex = { git = "https://github.com/moreal/num-complex", rev = "e179d6f7d4045ad9381249afb849fcb48831616c" } # REDOX START, Uncomment when you want to compile/check with redoxer # REDOX END @@ -192,7 +193,7 @@ malachite-bigint = "0.9.1" malachite-q = "0.9.1" malachite-base = "0.9.1" memchr = "2.8.0" -num-complex = "0.4.6" +num-complex = { git = "https://github.com/moreal/num-complex", rev = "e179d6f7d4045ad9381249afb849fcb48831616c" } num-integer = "0.1.46" num-traits = "0.2" num_enum = { version = "0.7", default-features = false } diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 4adfa0a60ad..0c9b3026f42 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -103,7 +103,6 @@ def check_div(self, x, y): q = z.__truediv__(y) self.assertClose(q, x) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: floats nan and inf are not identical def test_truediv(self): simple_real = [float(i) for i in range(-5, 6)] simple_complex = [complex(x, y) for x in simple_real for y in simple_real] @@ -290,7 +289,6 @@ def test_sub(self): self.assertRaises(TypeError, operator.sub, 1j, None) self.assertRaises(TypeError, operator.sub, None, 1j) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_mul(self): self.assertEqual(1j * int(20), complex(0, 20)) self.assertEqual(1j * int(-1), complex(0, -1)) diff --git a/crates/vm/src/builtins/complex.rs b/crates/vm/src/builtins/complex.rs index a284bf4e4d3..5f685ea9948 100644 --- a/crates/vm/src/builtins/complex.rs +++ b/crates/vm/src/builtins/complex.rs @@ -142,12 +142,43 @@ fn to_op_complex(value: &PyObject, vm: &VirtualMachine) -> PyResult PyResult { - if v2.is_zero() { - return Err(vm.new_zero_division_error("complex division by zero")); +/// Equivalent of CPython's `_Py_rc_quot`: real / complex division using +/// Smith's method with C99 Annex G.5.2 NaN recovery. +fn rc_div(a: f64, b: Complex64) -> Complex64 { + let abs_breal = b.re.abs(); + let abs_bimag = b.im.abs(); + + let (mut x, mut y); + + if abs_breal >= abs_bimag { + if abs_breal == 0.0 { + // zero division — caller should have checked + return Complex64::new(0.0, 0.0); + } + let ratio = b.im / b.re; + let denom = b.re + b.im * ratio; + x = a / denom; + y = (-a * ratio) / denom; + } else if abs_bimag >= abs_breal { + let ratio = b.re / b.im; + let denom = b.re * ratio + b.im; + x = (a * ratio) / denom; + y = (-a) / denom; + } else { + // At least one of b.re or b.im is a NaN + x = f64::NAN; + y = f64::NAN; + } + + if x.is_nan() && y.is_nan() && a.is_finite() && (abs_breal.is_infinite() || abs_bimag.is_infinite()) + { + let bx = if b.re.is_infinite() { 1.0_f64.copysign(b.re) } else { 0.0_f64.copysign(b.re) }; + let by = if b.im.is_infinite() { 1.0_f64.copysign(b.im) } else { 0.0_f64.copysign(b.im) }; + x = 0.0 * (a * bx); + y = 0.0 * (-a * by); } - Ok(v1.fdiv(v2)) + Complex64::new(x, y) } fn inner_pow(v1: Complex64, v2: Complex64, vm: &VirtualMachine) -> PyResult { @@ -468,7 +499,16 @@ impl AsNumber for PyComplex { vm, ) }), - multiply: Some(|a, b, vm| PyComplex::number_op(a, b, |a, b, _vm| a * b, vm)), + multiply: Some(|a, b, vm| { + PyComplex::complex_real_binop( + a, + b, + |a, b| a.fmul(b), + |a_complex, b_real| Complex64::new(a_complex.re * b_real, a_complex.im * b_real), + |a_real, b_complex| Complex64::new(a_real * b_complex.re, a_real * b_complex.im), + vm, + ) + }), power: Some(|a, b, c, vm| { if vm.is_none(c) { PyComplex::number_op(a, b, inner_pow, vm) @@ -493,7 +533,34 @@ impl AsNumber for PyComplex { result.to_pyresult(vm) }), boolean: Some(|number, _vm| Ok(!PyComplex::number_downcast(number).value.is_zero())), - true_divide: Some(|a, b, vm| PyComplex::number_op(a, b, inner_div, vm)), + true_divide: Some(|a, b, vm| { + PyComplex::complex_real_binop( + a, + b, + |a, b| -> PyResult { + if b.is_zero() { + Err(vm.new_zero_division_error("complex division by zero")) + } else { + Ok(a.fdiv(b)) + } + }, + |a_complex, b_real| -> PyResult { + if b_real == 0.0 { + Err(vm.new_zero_division_error("complex division by zero")) + } else { + Ok(Complex64::new(a_complex.re / b_real, a_complex.im / b_real)) + } + }, + |a_real, b_complex| -> PyResult { + if b_complex.is_zero() { + Err(vm.new_zero_division_error("complex division by zero")) + } else { + Ok(rc_div(a_real, b_complex)) + } + }, + vm, + ) + }), ..PyNumberMethods::NOT_IMPLEMENTED }; &AS_NUMBER