Skip to main content

rustpython_vm/builtins/
complex.rs

1use super::{PyStr, PyType, PyTypeRef, float};
2use crate::{
3    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
4    builtins::PyUtf8StrRef,
5    class::{PyClassDef, PyClassImpl},
6    common::{format::FormatSpec, wtf8::Wtf8Buf},
7    convert::{IntoPyException, ToPyObject, ToPyResult},
8    function::{FuncArgs, OptionalArg, PyComparisonValue},
9    protocol::PyNumberMethods,
10    stdlib::_warnings,
11    types::{AsNumber, Callable, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
12};
13use core::cell::Cell;
14use core::num::Wrapping;
15use core::ptr::NonNull;
16use num_complex::Complex64;
17use num_traits::Zero;
18use rustpython_common::hash;
19
20#[pyclass(module = false, name = "complex")]
21#[derive(Debug, Copy, Clone, PartialEq)]
22pub struct PyComplex {
23    #[pymember(name = "real", path = "re")]
24    #[pymember(name = "imag", path = "im")]
25    value: Complex64,
26}
27
28impl Py<PyComplex> {
29    #[must_use]
30    #[inline]
31    pub const fn as_complex(&self) -> Complex64 {
32        self.payload.to_complex()
33    }
34}
35
36// spell-checker:ignore MAXFREELIST
37thread_local! {
38    static COMPLEX_FREELIST: Cell<crate::object::FreeList<PyComplex>> = const { Cell::new(crate::object::FreeList::new()) };
39}
40
41impl PyPayload for PyComplex {
42    const MAX_FREELIST: usize = 100;
43    const HAS_FREELIST: bool = true;
44
45    #[inline]
46    fn class(ctx: &Context) -> &'static Py<PyType> {
47        ctx.types.complex_type
48    }
49
50    #[inline]
51    unsafe fn freelist_push(obj: *mut PyObject) -> bool {
52        COMPLEX_FREELIST
53            .try_with(|fl| {
54                let mut list = fl.take();
55                let stored = if list.len() < Self::MAX_FREELIST {
56                    list.push(obj);
57                    true
58                } else {
59                    false
60                };
61                fl.set(list);
62                stored
63            })
64            .unwrap_or(false)
65    }
66
67    #[inline]
68    unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
69        COMPLEX_FREELIST
70            .try_with(|fl| {
71                let mut list = fl.take();
72                let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
73                fl.set(list);
74                result
75            })
76            .ok()
77            .flatten()
78    }
79}
80
81impl ToPyObject for Complex64 {
82    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
83        PyComplex::from(self).to_pyobject(vm)
84    }
85}
86
87impl From<Complex64> for PyComplex {
88    fn from(value: Complex64) -> Self {
89        Self { value }
90    }
91}
92
93impl PyObjectRef {
94    /// Tries converting a python object into a complex, returns an option of whether the complex
95    /// and whether the  object was a complex originally or coerced into one
96    pub fn try_complex(&self, vm: &VirtualMachine) -> PyResult<Option<(Complex64, bool)>> {
97        if let Some(complex) = self.downcast_ref_if_exact::<PyComplex>(vm) {
98            return Ok(Some((complex.as_complex(), true)));
99        }
100        if let Some(method) = vm.get_method(self.clone(), identifier!(vm, __complex__)) {
101            let result = method?.call((), vm)?;
102
103            let ret_class = result.class().to_owned();
104            if let Some(ret) = result.downcast_ref::<PyComplex>()
105                && !result.class().is(vm.ctx.types.complex_type)
106            {
107                _warnings::warn(
108                    vm.ctx.exceptions.deprecation_warning,
109                    format!(
110                        "__complex__ returned non-complex (type {ret_class}).  \
111                    The ability to return an instance of a strict subclass of complex \
112                    is deprecated, and may be removed in a future version of Python."
113                    ),
114                    1,
115                    vm,
116                )?;
117
118                return Ok(Some((ret.as_complex(), true)));
119            }
120
121            return match result.downcast_ref::<PyComplex>() {
122                Some(complex_obj) => Ok(Some((complex_obj.as_complex(), true))),
123                None => Err(vm.new_type_error(format!(
124                    "__complex__ returned non-complex (type '{}')",
125                    result.class().name()
126                ))),
127            };
128        }
129        // `complex` does not have a `__complex__` by default, so subclasses might not either,
130        // use the actual stored value in this case
131        if let Some(complex) = self.downcast_ref::<PyComplex>() {
132            return Ok(Some((complex.as_complex(), true)));
133        }
134
135        if let Some(float) = self.try_float_opt(vm) {
136            return Ok(Some((Complex64::new(float?.to_f64(), 0.0), false)));
137        }
138
139        Ok(None)
140    }
141}
142
143pub(crate) fn init(context: &'static Context) {
144    PyComplex::extend_class(context, context.types.complex_type);
145}
146
147fn to_op_complex(value: &PyObject, vm: &VirtualMachine) -> PyResult<Option<Complex64>> {
148    let r = if let Some(complex) = value.downcast_ref::<PyComplex>() {
149        Some(complex.as_complex())
150    } else {
151        float::to_op_float(value, vm)?.map(|float| Complex64::new(float, 0.0))
152    };
153    Ok(r)
154}
155
156const ONE: Complex64 = Complex64::new(1.0, 0.0);
157
158/// Only the magnitude of an infinite part matters once a product has gone to
159/// nan, so it stands in as a signed one; a nan beside it stands in as a
160/// signed zero.
161fn signed_unit(part: f64) -> f64 {
162    if part.is_infinite() { 1.0 } else { 0.0f64 }.copysign(part)
163}
164
165fn tamed(value: Complex64) -> Complex64 {
166    Complex64::new(signed_unit(value.re), signed_unit(value.im))
167}
168
169fn nans_to_zero(value: Complex64) -> Complex64 {
170    let zeroed = |part: f64| {
171        if part.is_nan() {
172            0.0f64.copysign(part)
173        } else {
174            part
175        }
176    };
177    Complex64::new(zeroed(value.re), zeroed(value.im))
178}
179
180/// Multiply, recovering the infinities that the plain formula turns into nan,
181/// the way C11 Annex G.5.1 does.
182fn prod(a: Complex64, b: Complex64) -> Complex64 {
183    let r = a * b;
184    if !(r.re.is_nan() && r.im.is_nan()) {
185        return r;
186    }
187
188    // "Box" an infinite operand into a signed one and turn the other's nans
189    // into signed zeros, so that the infinity survives a second pass.
190    let (mut a, mut b) = (a, b);
191    let a_infinite = a.re.is_infinite() || a.im.is_infinite();
192    if a_infinite {
193        a = tamed(a);
194        b = nans_to_zero(b);
195    }
196    let b_infinite = b.re.is_infinite() || b.im.is_infinite();
197    if b_infinite {
198        b = tamed(b);
199        a = nans_to_zero(a);
200    }
201    // An infinity that overflow lost, rather than one an operand carried.
202    let overflowed = !a_infinite
203        && !b_infinite
204        && ((a.re * b.re).is_infinite()
205            || (a.im * b.im).is_infinite()
206            || (a.re * b.im).is_infinite()
207            || (a.im * b.re).is_infinite());
208    if overflowed {
209        a = nans_to_zero(a);
210        b = nans_to_zero(b);
211    }
212
213    if !(a_infinite || b_infinite || overflowed) {
214        return r;
215    }
216    Complex64::new(
217        f64::INFINITY * (a.re * b.re - a.im * b.im),
218        f64::INFINITY * (a.re * b.im + a.im * b.re),
219    )
220}
221
222/// Divide a real by a complex. Written out rather than routed through `quot`
223/// with a zero imaginary part, which would lose the sign of a zero.
224fn rc_quot(a: f64, b: Complex64) -> Option<Complex64> {
225    let abs_re = b.re.abs();
226    let abs_im = b.im.abs();
227
228    let r = if abs_re >= abs_im {
229        if abs_re == 0.0 {
230            return None;
231        }
232        let ratio = b.im / b.re;
233        let denom = b.re + b.im * ratio;
234        Complex64::new(a / denom, -a * ratio / denom)
235    } else if abs_im >= abs_re {
236        let ratio = b.re / b.im;
237        let denom = b.re * ratio + b.im;
238        Complex64::new(a * ratio / denom, -a / denom)
239    } else {
240        // One part of the divisor is a nan, so neither comparison held.
241        Complex64::new(f64::NAN, f64::NAN)
242    };
243
244    // A quotient that came out as nan recovers the way `recovered_quot` does,
245    // except that a real numerator has no imaginary term to carry a sign.
246    if r.re.is_nan() && r.im.is_nan() && (b.re.is_infinite() || b.im.is_infinite()) && a.is_finite()
247    {
248        let Complex64 { re: x, im: y } = tamed(b);
249        return Some(Complex64::new(0.0 * (a * x), 0.0 * -(a * y)));
250    }
251
252    Some(r)
253}
254
255/// Divide by whichever part of the divisor is larger, so that the ratio the
256/// other part is scaled by cannot overflow. `None` stands for a divisor of
257/// zero.
258fn quot(a: Complex64, b: Complex64) -> Option<Complex64> {
259    let abs_re = b.re.abs();
260    let abs_im = b.im.abs();
261
262    let r = if abs_re >= abs_im {
263        if abs_re == 0.0 {
264            return None;
265        }
266        let ratio = b.im / b.re;
267        let denom = b.re + b.im * ratio;
268        Complex64::new((a.re + a.im * ratio) / denom, (a.im - a.re * ratio) / denom)
269    } else if abs_im >= abs_re {
270        let ratio = b.re / b.im;
271        let denom = b.re * ratio + b.im;
272        Complex64::new((a.re * ratio + a.im) / denom, (a.im * ratio - a.re) / denom)
273    } else {
274        // One part of the divisor is a nan, so neither comparison held.
275        Complex64::new(f64::NAN, f64::NAN)
276    };
277
278    Some(recovered_quot(r, a, b))
279}
280
281/// Recover the infinities and zeros a quotient came out of as nan, the way
282/// C11 Annex G.5.2 does.
283fn recovered_quot(r: Complex64, a: Complex64, b: Complex64) -> Complex64 {
284    if !(r.re.is_nan() && r.im.is_nan()) {
285        return r;
286    }
287
288    if (a.re.is_infinite() || a.im.is_infinite()) && b.re.is_finite() && b.im.is_finite() {
289        let Complex64 { re: x, im: y } = tamed(a);
290        Complex64::new(
291            f64::INFINITY * (x * b.re + y * b.im),
292            f64::INFINITY * (y * b.re - x * b.im),
293        )
294    } else if (b.re.is_infinite() || b.im.is_infinite()) && a.re.is_finite() && a.im.is_finite() {
295        let Complex64 { re: x, im: y } = tamed(b);
296        Complex64::new(0.0 * (a.re * x + a.im * y), 0.0 * (a.im * x - a.re * y))
297    } else {
298        r
299    }
300}
301
302fn inner_div(v1: Complex64, v2: Complex64, vm: &VirtualMachine) -> PyResult<Complex64> {
303    quot(v1, v2).ok_or_else(|| vm.new_zero_division_error("division by zero"))
304}
305
306/// Raise to a non-negative integer power by repeated squaring.
307fn pow_unsigned(x: Complex64, n: u32) -> Complex64 {
308    let mut r = ONE;
309    let mut p = x;
310    let mut mask = 1u32;
311    while mask > 0 && n >= mask {
312        if n & mask != 0 {
313            r = prod(r, p);
314        }
315        mask <<= 1;
316        p = prod(p, p);
317    }
318    r
319}
320
321/// Raise to an integer power. `None` stands for zero raised to a negative one.
322fn powi(x: Complex64, n: i32) -> Option<Complex64> {
323    if n > 0 {
324        Some(pow_unsigned(x, n as u32))
325    } else {
326        quot(ONE, pow_unsigned(x, n.unsigned_abs()))
327    }
328}
329
330pub(crate) fn complex_pow(
331    v1: Complex64,
332    v2: Complex64,
333    vm: &VirtualMachine,
334) -> PyResult<Complex64> {
335    // A small integer power is reached by multiplying, which stays exact
336    // where going through the polar form would not.
337    let exponent = v2.re as i32;
338    let result = if v2.im == 0.0 && v2.re == f64::from(exponent) && v2.re.abs() <= 100.0 {
339        powi(v1, exponent)
340    } else if v1.is_zero() && (v2.im != 0.0 || v2.re < 0.0) {
341        None
342    } else {
343        Some(powc(v1, v2))
344    };
345
346    let Some(result) = result else {
347        return Err(vm.new_zero_division_error("zero to a negative or complex power"));
348    };
349    if result.re.is_infinite() || result.im.is_infinite() {
350        return Err(vm.new_overflow_error("complex exponentiation"));
351    }
352    Ok(result)
353}
354
355/// Raise to a power through the polar form.
356fn powc(a: Complex64, exp: Complex64) -> Complex64 {
357    if exp.is_zero() {
358        return ONE;
359    }
360    if a.is_zero() {
361        return Complex64::new(0.0, 0.0);
362    }
363
364    let magnitude = a.norm();
365    let angle = a.arg();
366    let mut len = magnitude.powf(exp.re);
367    let mut phase = angle * exp.re;
368    // An exponent with no imaginary part leaves these alone, and reaching for
369    // them anyway would turn an infinite magnitude into a nan.
370    if exp.im != 0.0 {
371        len *= (-angle * exp.im).exp();
372        phase += exp.im * magnitude.ln();
373    }
374    Complex64::new(len * phase.cos(), len * phase.sin())
375}
376
377/// Whether an underscore sits where a numeric literal does not allow one:
378/// they only ever join two digits.
379fn has_misplaced_underscore(bytes: &[u8]) -> bool {
380    let mut prev = b'\0';
381    for &byte in bytes {
382        if byte == b'_' {
383            if !prev.is_ascii_digit() {
384                return true;
385            }
386        } else if prev == b'_' && !byte.is_ascii_digit() {
387            return true;
388        }
389        prev = byte;
390    }
391    prev == b'_'
392}
393
394impl Constructor for PyComplex {
395    type Args = ComplexArgs;
396
397    fn slot_new(cls: PyTypeRef, func_args: FuncArgs, vm: &VirtualMachine) -> PyResult {
398        // Optimization: return exact complex as-is (only when imag is not provided)
399        if cls.is(vm.ctx.types.complex_type)
400            && func_args.args.len() == 1
401            && func_args.kwargs.is_empty()
402            && func_args.args[0].class().is(vm.ctx.types.complex_type)
403        {
404            return Ok(func_args.args[0].clone());
405        }
406
407        let args: Self::Args = func_args.bind_for(vm, Self::NAME)?;
408        let payload = Self::py_new(&cls, args, vm)?;
409        payload.into_ref_with_type(vm, cls).map(Into::into)
410    }
411
412    fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
413        let imag_missing = args.imag.is_missing();
414        let (real, real_was_complex) = match args.real {
415            OptionalArg::Missing => (Complex64::new(0.0, 0.0), false),
416            OptionalArg::Present(val) => {
417                if let Some(c) = val.try_complex(vm)? {
418                    c
419                } else if let Some(s) = val.downcast_ref::<PyStr>() {
420                    if args.imag.is_present() {
421                        return Err(vm.new_type_error(
422                            "complex() can't take second arg if first is a string",
423                        ));
424                    }
425                    if has_misplaced_underscore(s.as_wtf8().as_bytes()) {
426                        let repr = val.repr(vm)?;
427                        return Err(vm.new_value_error(format!(
428                            "could not convert string to complex: {repr}"
429                        )));
430                    }
431                    let (re, im) = rustpython_literal::complex::parse_str(
432                        &crate::protocol::numeric_literal_from_str(s),
433                    )
434                    .ok_or_else(|| vm.new_value_error("complex() arg is a malformed string"))?;
435                    return Ok(Self::from(Complex64 { re, im }));
436                } else {
437                    return Err(vm.new_type_error(format!(
438                        "complex() argument must be a string or a number, not {}",
439                        val.class().slot_name()
440                    )));
441                }
442            }
443        };
444
445        let (imag, imag_was_complex) = match args.imag {
446            // Copy the imaginary from the real to the real of the imaginary
447            // if an  imaginary argument is not passed in
448            OptionalArg::Missing => (Complex64::new(real.im, 0.0), false),
449            OptionalArg::Present(obj) => {
450                if let Some(c) = obj.try_complex(vm)? {
451                    c
452                } else if obj.class().fast_issubclass(vm.ctx.types.str_type) {
453                    return Err(vm.new_type_error("complex() second arg can't be a string"));
454                } else {
455                    return Err(vm.new_type_error(format!(
456                        "complex() second argument must be a number, not '{}'",
457                        obj.class().name()
458                    )));
459                }
460            }
461        };
462
463        let final_real = if imag_was_complex {
464            real.re - imag.im
465        } else {
466            real.re
467        };
468
469        let final_imag = if real_was_complex && !imag_missing {
470            imag.re + real.im
471        } else {
472            imag.re
473        };
474        let value = Complex64::new(final_real, final_imag);
475        Ok(Self::from(value))
476    }
477}
478
479impl PyComplex {
480    #[deprecated(note = "use PyComplex::from(...).into_ref() instead")]
481    pub fn new_ref(value: Complex64, ctx: &Context) -> PyRef<Self> {
482        Self::from(value).into_ref(ctx)
483    }
484
485    #[must_use]
486    pub const fn to_complex64(self) -> Complex64 {
487        self.value
488    }
489
490    #[must_use]
491    pub const fn to_complex(&self) -> Complex64 {
492        self.value
493    }
494
495    fn number_op<F, R>(a: &PyObject, b: &PyObject, op: F, vm: &VirtualMachine) -> PyResult
496    where
497        F: FnOnce(Complex64, Complex64, &VirtualMachine) -> R,
498        R: ToPyResult,
499    {
500        if let (Some(a), Some(b)) = (to_op_complex(a, vm)?, to_op_complex(b, vm)?) {
501            op(a, b, vm).to_pyresult(vm)
502        } else {
503            Ok(vm.ctx.not_implemented())
504        }
505    }
506
507    fn complex_real_binop<CCF, RCF, CRF, R>(
508        a: &PyObject,
509        b: &PyObject,
510        cc_op: CCF,
511        cr_op: CRF,
512        rc_op: RCF,
513        vm: &VirtualMachine,
514    ) -> PyResult
515    where
516        CCF: FnOnce(Complex64, Complex64) -> R,
517        CRF: FnOnce(Complex64, f64) -> R,
518        RCF: FnOnce(f64, Complex64) -> R,
519        R: ToPyResult,
520    {
521        let value = match (a.downcast_ref::<Self>(), b.downcast_ref::<Self>()) {
522            // complex + complex
523            (Some(a_complex), Some(b_complex)) => {
524                cc_op(a_complex.as_complex(), b_complex.as_complex())
525            }
526            (Some(a_complex), None) => {
527                let Some(b_real) = float::to_op_float(b, vm)? else {
528                    return Ok(vm.ctx.not_implemented());
529                };
530
531                // complex + real
532                cr_op(a_complex.as_complex(), b_real)
533            }
534            (None, Some(b_complex)) => {
535                let Some(a_real) = float::to_op_float(a, vm)? else {
536                    return Ok(vm.ctx.not_implemented());
537                };
538
539                // real + complex
540                rc_op(a_real, b_complex.as_complex())
541            }
542            (None, None) => return Ok(vm.ctx.not_implemented()),
543        };
544        value.to_pyresult(vm)
545    }
546}
547
548#[pyclass(
549    flags(BASETYPE),
550    with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable)
551)]
552impl Py<PyComplex> {
553    #[pymethod]
554    fn conjugate(&self) -> Complex64 {
555        self.value.conj()
556    }
557
558    #[pymethod]
559    fn __getnewargs__(&self) -> (f64, f64) {
560        let Complex64 { re, im } = self.value;
561        (re, im)
562    }
563
564    #[pymethod]
565    fn __format__(
566        zelf: &Self,
567        format_spec: PyUtf8StrRef,
568        vm: &VirtualMachine,
569    ) -> PyResult<Wtf8Buf> {
570        // Empty format spec: equivalent to str(self)
571        if format_spec.is_empty() {
572            return Ok(zelf.as_object().str(vm)?.as_wtf8().to_owned());
573        }
574        let format_spec =
575            FormatSpec::parse(format_spec.as_str()).map_err(|err| err.into_pyexception(vm))?;
576        let result = if format_spec.has_locale_format() {
577            let locale = crate::format::get_locale_info();
578            format_spec.format_complex_locale(&zelf.as_complex(), &locale)
579        } else {
580            format_spec.format_complex(&zelf.as_complex())
581        };
582        result
583            .map(Wtf8Buf::from_string)
584            .map_err(|err| err.into_pyexception(vm))
585    }
586
587    #[pyclassmethod]
588    fn from_number(cls: PyTypeRef, number: PyObjectRef, vm: &VirtualMachine) -> PyResult {
589        if number.class().is(vm.ctx.types.complex_type) && cls.is(vm.ctx.types.complex_type) {
590            return Ok(number);
591        }
592        let value = number
593            .try_complex(vm)?
594            .ok_or_else(|| {
595                vm.new_type_error(format!(
596                    "must be real number, not {}",
597                    number.class().name()
598                ))
599            })?
600            .0;
601        let result = vm.ctx.new_complex(value);
602        if cls.is(vm.ctx.types.complex_type) {
603            Ok(result.into())
604        } else {
605            PyType::call(&cls, vec![result.into()].into(), vm)
606        }
607    }
608}
609
610#[pyclass]
611impl PyRef<PyComplex> {
612    #[pymethod]
613    fn __complex__(self, vm: &VirtualMachine) -> Self {
614        if self.is(vm.ctx.types.complex_type) {
615            self
616        } else {
617            PyComplex::from(self.as_complex()).into_ref(&vm.ctx)
618        }
619    }
620}
621
622impl Comparable for PyComplex {
623    fn cmp(
624        zelf: &Py<Self>,
625        other: &PyObject,
626        op: PyComparisonOp,
627        vm: &VirtualMachine,
628    ) -> PyResult<PyComparisonValue> {
629        op.eq_only(|| {
630            let result = if let Some(other) = other.downcast_ref::<Self>() {
631                zelf.as_complex() == other.as_complex()
632            } else {
633                match float::to_op_float(other, vm) {
634                    Ok(Some(other)) => zelf.as_complex() == other.into(),
635                    Err(_) => false,
636                    Ok(None) => return Ok(PyComparisonValue::NotImplemented),
637                }
638            };
639            Ok(PyComparisonValue::Implemented(result))
640        })
641    }
642}
643
644impl Hashable for PyComplex {
645    #[inline]
646    fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<hash::PyHash> {
647        let value = zelf.as_complex();
648
649        let re_hash =
650            hash::hash_float(value.re).unwrap_or_else(|| hash::hash_object_id(zelf.get_id()));
651
652        let im_hash =
653            hash::hash_float(value.im).unwrap_or_else(|| hash::hash_object_id(zelf.get_id()));
654
655        let Wrapping(ret) = Wrapping(re_hash) + Wrapping(im_hash) * Wrapping(hash::IMAG);
656        Ok(hash::fix_sentinel(ret))
657    }
658}
659
660impl AsNumber for PyComplex {
661    fn as_number() -> &'static PyNumberMethods {
662        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
663            add: Some(|a, b, vm| {
664                PyComplex::complex_real_binop(
665                    a,
666                    b,
667                    |a, b| a + b,
668                    |a_complex, b_real| Complex64::new(a_complex.re + b_real, a_complex.im),
669                    |a_real, b_complex| Complex64::new(a_real + b_complex.re, b_complex.im),
670                    vm,
671                )
672            }),
673            subtract: Some(|a, b, vm| {
674                PyComplex::complex_real_binop(
675                    a,
676                    b,
677                    |a, b| a - b,
678                    |a_complex, b_real| Complex64::new(a_complex.re - b_real, a_complex.im),
679                    |a_real, b_complex| Complex64::new(a_real - b_complex.re, -b_complex.im),
680                    vm,
681                )
682            }),
683            multiply: Some(|a, b, vm| {
684                PyComplex::complex_real_binop(
685                    a,
686                    b,
687                    prod,
688                    |a_complex, b_real| {
689                        Complex64::new(a_complex.re * b_real, a_complex.im * b_real)
690                    },
691                    |a_real, b_complex| {
692                        Complex64::new(a_real * b_complex.re, a_real * b_complex.im)
693                    },
694                    vm,
695                )
696            }),
697            power: Some(|a, b, c, vm| {
698                if vm.is_none(c) {
699                    PyComplex::number_op(a, b, complex_pow, vm)
700                } else {
701                    Err(vm.new_value_error(String::from("complex modulo")))
702                }
703            }),
704            negative: Some(|number, vm| {
705                let value = PyComplex::number_downcast(number).as_complex();
706                (-value).to_pyresult(vm)
707            }),
708            positive: Some(|number, vm| {
709                PyComplex::number_downcast_exact(number, vm).to_pyresult(vm)
710            }),
711            absolute: Some(|number, vm| {
712                let value = PyComplex::number_downcast(number).as_complex();
713                let result = value.norm();
714                // Check for overflow: hypot returns inf for finite inputs that overflow
715                if result.is_infinite() && value.re.is_finite() && value.im.is_finite() {
716                    return Err(vm.new_overflow_error("absolute value too large"));
717                }
718                result.to_pyresult(vm)
719            }),
720            boolean: Some(|number, _vm| {
721                Ok(!PyComplex::number_downcast(number).as_complex().is_zero())
722            }),
723            true_divide: Some(|a, b, vm| {
724                PyComplex::complex_real_binop(
725                    a,
726                    b,
727                    |a, b| inner_div(a, b, vm),
728                    |a_complex, b_real| {
729                        if b_real == 0.0 {
730                            Err(vm.new_zero_division_error("division by zero"))
731                        } else {
732                            Ok(Complex64::new(a_complex.re / b_real, a_complex.im / b_real))
733                        }
734                    },
735                    |a_real, b_complex| {
736                        rc_quot(a_real, b_complex)
737                            .ok_or_else(|| vm.new_zero_division_error("division by zero"))
738                    },
739                    vm,
740                )
741            }),
742            ..PyNumberMethods::NOT_IMPLEMENTED
743        };
744        &AS_NUMBER
745    }
746
747    fn clone_exact(zelf: &Py<Self>, vm: &VirtualMachine) -> PyRef<Self> {
748        vm.ctx.new_complex(zelf.as_complex())
749    }
750}
751
752impl Representable for PyComplex {
753    #[inline]
754    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
755        // TODO: when you fix this, move it to rustpython_common::complex::repr and update
756        //       ast/src/unparse.rs + impl Display for Constant in ast/src/constant.rs
757        let Complex64 { re, im } = zelf.as_complex();
758        Ok(rustpython_literal::complex::to_string(re, im))
759    }
760}
761
762#[derive(FromArgs)]
763pub struct ComplexArgs {
764    // Missing real is 0. A string real uses the imag-missing path.
765    #[pyarg(any, default, py_default = "0")]
766    real: OptionalArg<PyObjectRef>,
767    // Missing imag is 0 and is distinct from imag=0 when real is a string.
768    #[pyarg(any, default, py_default = "0")]
769    imag: OptionalArg<PyObjectRef>,
770}