You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Small integer powers of real±0j are invalid #117999
Huh. There is a bug, but a different one from this bugreport: 1-0jis not a complex(1,-0.0). See this thread. In short, a±bj is not an equivalent of rectangular notation for complex numbers if components are special floating point numbers (signed zeros, nans, infinities).
What is a bug here: complex(1,-0.0)**2 and complex(1,-0.0)*complex(1,-0.0) have different imaginary components:
They are equal in sense of == (just as 0.0==-0.0), but you can see the difference in the repr() output (or you could use math.copysign() to test this).
Consider also cases when the real part is ±0 and the imaginary part is ±1. [...] And there are also interesting cases when one of components is ±inf and other is ±1, or when both components are ±inf.
In short, all special numbers (i.e. when any component is infinity, signed zero or nan) require a special treatment. Iff... this is a bug.
@serhiy-storchaka, should I fill a separate report for _Py_c_quot()? Given
It is a different case. 1/complex(1, 0.) is currently complex(1.0, 0.0)/complex(1.0, 0.0). The sign of the imaginary part of the result depends on signs of imaginary parts of the nominator and the denominator. Since 0.0 - 0.0 and 0.0 + -0.0 are 0.0, I think that the current result is correct. When (if) we implement mixed real-complex operations separately, the right result of 1/complex(1, 0.) will be complex(1.0, -0.0). But it is a part of much larger issue, all arithmetic operations will be affected.
I was thinking about definition of the inversion z from z*q = 1 == 1+0j. For z=1+0j current arithmetic rules allow both q=z and q=complex(1,-0.0). So, we should use something else (e.g continuity argument) to choose the answer.
I agree, with the mixed-mode arithmetic this decision is trivial: "just use 1/z". Perhaps, this is one argument for adding imaginary type, that was not mentioned in the d.p.o thread, unfortunately: i.e. preservation of analytic identities. For example:
The C variant (on gcc+glibc) has no such issue (yet cpow() looks buggy just as CPython's pow() in this issue, hence z*z), because mixed-mode arithmetic rules at least for complex and real pairs, see this.
Ok, I did update of the pr to fix only nonnegative powers. I think, it works, here are few "differences" (which are due to different arrangements of parenthesis in the c_powu(), see below):
The problem in the current algorithm for small integer degree is that it assumes that c**0 is c_1 = complex(1.0, 0.0) for every complex number c. But it should be complex(1.0, -0.0) for complex(1.0, -0.0). There is a similar error in calculation for the negative integer degree.
See also #124243 which fixes calculation of small integer powers of complex numbers. It guarantees that c**1 is c for any complex number c. c**2 is now equivalent to c*c. etc (if no OverflowError was raised).
Your MPC example is version dependent: x**8 is mpc('16.0+0.0j') with MPC 1.3.1, but mpc('16.0-0.0j'), as in #124243, with MPC 1.4.0. From the MPC 1.4.0 NEWS: "mpc_pow: Agree on and implement the sign of the imaginary part when both inputs are real".
MPC does not implement complex(x, y)**0 = complex(1.0, copysign(0.0, atan2(y, x))) either: mpc(1.0,-0.0)**0 is mpc('1.0-0.0j'), but mpc(2.0,-0.0)**0 is mpc('1.0+0.0j') — the sign depends on the modulus, not on the argument. Let us keep 1+0j and consider the zero exponent in gh-137746.
Activity
gaogaotiantian commentedon Apr 17, 2024
Is
(1-0j)**2 == 1+0ja bug? In another way, is1+0jdifferent than1-0j?pythongh-117999: fixed invalid small integer powers of real±0j
skirpichev commentedon Apr 17, 2024
Huh. There is a bug, but a different one from this bugreport:
1-0jis not acomplex(1,-0.0). See this thread. In short,a±bjis not an equivalent of rectangular notation for complex numbers if components are special floating point numbers (signed zeros, nans, infinities).What is a bug here:
complex(1,-0.0)**2andcomplex(1,-0.0)*complex(1,-0.0)have different imaginary components:They are equal in sense of
==(just as0.0==-0.0), but you can see the difference in therepr()output (or you could usemath.copysign()to test this).pythongh-117999: fixed invalid small integer powers of real±0j
skirpichev commentedon Apr 18, 2024
The
_Py_c_quot()is affected as well, not sure if this worth a separate issue:nineteendo commentedon Apr 18, 2024
Wait until a decision has been made for the existing pull request. That avoids unnecessary review churn.
serhiy-storchaka commentedon Apr 23, 2024
Consider also cases when the real part is -1.
Consider also cases when the real part is ±0 and the imaginary part is ±1.
And there are also interesting cases when one of components is ±inf and other is ±1, or when both components are ±inf.
skirpichev commentedon Apr 23, 2024
Thanks, that was missed.
In short, all special numbers (i.e. when any component is infinity, signed zero or nan) require a special treatment. Iff... this is a bug.
@serhiy-storchaka, should I fill a separate report for
_Py_c_quot()? Givenserhiy-storchaka commentedon Apr 23, 2024
It is a different case.
1/complex(1, 0.)is currentlycomplex(1.0, 0.0)/complex(1.0, 0.0). The sign of the imaginary part of the result depends on signs of imaginary parts of the nominator and the denominator. Since0.0 - 0.0and0.0 + -0.0are0.0, I think that the current result is correct. When (if) we implement mixed real-complex operations separately, the right result of1/complex(1, 0.)will becomplex(1.0, -0.0). But it is a part of much larger issue, all arithmetic operations will be affected.skirpichev commentedon Apr 24, 2024
Then we have discontinuity, e.g.
I was thinking about definition of the inversion
zfromz*q = 1 == 1+0j. Forz=1+0jcurrent arithmetic rules allow bothq=zandq=complex(1,-0.0). So, we should use something else (e.g continuity argument) to choose the answer.I agree, with the mixed-mode arithmetic this decision is trivial: "just use
1/z". Perhaps, this is one argument for adding imaginary type, that was not mentioned in the d.p.o thread, unfortunately: i.e. preservation of analytic identities. For example:The C variant (on gcc+glibc) has no such issue (yet
cpow()looks buggy just as CPython'spow()in this issue, hencez*z), because mixed-mode arithmetic rules at least for complex and real pairs, see this.skirpichev commentedon Apr 24, 2024
Ok, I did update of the pr to fix only nonnegative powers. I think, it works, here are few "differences" (which are due to different arrangements of parenthesis in the
c_powu(), see below):script code
For illustration of a failing example:
As associativity is broken already for floats - I think it's fine.
pythongh-117999: fixed small nonnegative integer powers of complex nu…
pythongh-117999: fixed small nonnegative integer powers of complex nu…
pythongh-117999: fixed small nonnegative integer powers of complex nu…
6 remaining items
serhiy-storchaka commentedon Aug 25, 2024
The problem in the current algorithm for small integer degree is that it assumes that
c**0isc_1 = complex(1.0, 0.0)for every complex numberc. But it should becomplex(1.0, -0.0)forcomplex(1.0, -0.0). There is a similar error in calculation for the negative integer degree.For$c = r e^{i\phi}$ , $\lim_{\varepsilon \to 0}{c^\varepsilon} = \lim_{\varepsilon \to 0}{e^{\varepsilon(\ln{r}+i\phi)}} = \lim_{\varepsilon \to 0}{e^{\varepsilon\ln{r}}} \cdot \lim_{\varepsilon \to 0}{e^{i\varepsilon\phi}} = \lim_{\varepsilon \to 0}{e^{i\varepsilon\phi}} = 1 + i\lim_{\varepsilon \to 0}{\varepsilon\phi}$ .
So,
complex(x, y)**0should becomplex(1.0, copysign(0.0, atan2(y, x)).skirpichev commentedon Aug 26, 2024
Unfortunately, I doubt that binary exponentiation can be easily (if at all) fixed to address this. Another example from:
pythongh-117999: Fix small integer powers of complex numbers
serhiy-storchaka commentedon Sep 19, 2024
See also #124243 which fixes calculation of small integer powers of complex numbers. It guarantees that
c**1iscfor any complex numberc.c**2is now equivalent toc*c. etc (if no OverflowError was raised).skirpichev commentedon Aug 18, 2026
Unfortunately, this is not true for any
c(regardless on overflows). That fail for values like shown above:Note, that this also holds for the MPC, you can use gmpy2 to see it:
I don't see an easy way to solve this. (Above cancellation is just a simplest example, in one binop.)
So, I think it's fine for now. @serhiy-storchaka, lets merge either #124243 or #118000.
serhiy-storchaka commentedon Sep 3, 2026
x**6is calculated as(x*x)*(x*x)*(x*x), andx**8as((x*x)*(x*x))*((x*x)*(x*x)). This is more accurate even for floats.serhiy-storchaka commentedon Sep 3, 2026
Your MPC example is version dependent:
x**8ismpc('16.0+0.0j')with MPC 1.3.1, butmpc('16.0-0.0j'), as in #124243, with MPC 1.4.0. From the MPC 1.4.0 NEWS: "mpc_pow: Agree on and implement the sign of the imaginary part when both inputs are real".MPC does not implement
complex(x, y)**0=complex(1.0, copysign(0.0, atan2(y, x)))either:mpc(1.0,-0.0)**0ismpc('1.0-0.0j'), butmpc(2.0,-0.0)**0ismpc('1.0+0.0j')— the sign depends on the modulus, not on the argument. Let us keep1+0jand consider the zero exponent in gh-137746.gh-117999: Fix small integer powers of complex numbers (GH-124243)
skirpichev commentedon Sep 3, 2026
See also #60200.
pythongh-117999: Fix small integer powers of complex numbers (pythonG…