-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Small integer powers of real±0j are invalid #117999
Comments
Is |
Huh. There is a bug, but a different one from this bugreport: What is a bug here: >>> complex(1,-0.0)**2
(1+0j)
>>> complex(1,-0.0)*complex(1,-0.0)
(1-0j) They are equal in sense of |
The >>> 1/complex(1, 0.) # should be (1-0j)
(1+0j)
>>> 1/complex(1, -0.)
(1+0j)
>>> complex(1, 0.)**-1.00001
(1-0j)
>>> complex(1, 0.)**-0.99999
(1-0j)
>>> complex(1, -0.)**-1.00001
(1+0j)
>>> complex(1, -0.)**-0.99999
(1+0j) |
Wait until a decision has been made for the existing pull request. That avoids unnecessary review churn. |
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. |
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 >>> complex(1, 0.0)*complex(1, 0.0)
(1+0j)
>>> complex(1, 0.0)*complex(1, -0.0)
(1+0j) |
It is a different case. |
Then we have discontinuity, e.g. >>> complex(1,-0.0)**1.0000001
(1-0j)
>>> complex(1,-0.0)**1 # oops
(1+0j)
>>> complex(1,-0.0)**0.9999999
(1-0j)
I was thinking about definition of the inversion I agree, with the mixed-mode arithmetic this decision is trivial: "just use
The C variant (on gcc+glibc) has no such issue (yet |
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
script code# a.py
from functools import reduce
from itertools import combinations
from math import inf, nan
cases = [complex(*x) for x in combinations([1, -1, 0.0, -0.0, 2,
inf, -inf, nan], 2)]
powers = [0, 1, 2, 3, 4, 5, 6]
for c in cases:
for p in powers:
try:
r_pow = repr(c**p)
except OverflowError:
continue
r_pro = repr(reduce(lambda x, y: x*y, [c]*p) if p else 1+0j)
if r_pow != r_pro:
print(f'{c}**{p}: ', r_pow, " product: ", r_pro) For illustration of a failing example: >>> z = 1-1j
>>> z*z
-2j
>>> z*z*z*z
(-4+0j)
>>> (1+0j)*(z*z)*(z*z) # z**4 implementation
(-4-0j) As associativity is broken already for floats - I think it's fine. |
…mbers Before, handling of numbers with special values in components (infinities, nans, signed zero) was invalid. Simple example: >>> z = complex(1, -0.0) >>> z*z (1-0j) >>> z**2 (1+0j) Now: >>> z**2 (1-0j)
…mbers Before, handling of numbers with special values in components (infinities, nans, signed zero) was invalid. Simple example: >>> z = complex(1, -0.0) >>> z*z (1-0j) >>> z**2 (1+0j) Now: >>> z**2 (1-0j)
…mbers Before, handling of numbers with special values in components (infinities, nans, signed zero) was invalid. Simple example: >>> z = complex(1, -0.0) >>> z*z (1-0j) >>> z**2 (1+0j) Now: >>> z**2 (1-0j)
…mbers Before, handling of numbers with special values in components (infinities, nans, signed zero) was invalid. Simple example: >>> z = complex(1, -0.0) >>> z*z (1-0j) >>> z**2 (1+0j) Now: >>> z**2 (1-0j)
Hmm, it seems that similar issue for negative (small) integer exponents can't be solved without mixed-mode arithmetic in some parts. Example in the main: >>> z = 1+0j
>>> z**-111.0000001
(1-0j)
>>> z**-111 # big enough to use generic power algorithm
(1-0j)
>>> z**-110.9999999
(1-0j)
>>> z**-1.0000001
(1-0j)
>>> z**-1 # oops
(1+0j)
>>> c_pow(z, -1) # with generic algorithm in python
(1-0j)
>>> z**-0.9999999
(1-0j) Same happens for other special components. This seems to be an issue, regardless on the question of how we define e.g. The problem part in this case is this line: cpython/Objects/complexobject.c Line 181 in 3c890b5
Assuming that c_powu() is fixed, true division on this line should be implemented using mixed-mode arithmetic rules (real / complex), i.e. 1/z=complex(z.real,-z.imag)/abs(z)**2 .
pure-python version of generic pow() algorithmdef c_pow(a, b):
from math import atan2, cos, exp, hypot, log, sin
if b.real == b.imag == 0:
return 1+0j
elif a.real == a.imag == 0:
if b.imag or b.real < 0:
raise ZeroDivisionError
return 0j
vabs = hypot(a.real, a.imag)
len = pow(vabs, b.real)
at = atan2(a.imag, a.real)
phase = at*b.real
if b.imag:
len /= exp(at*b.imag)
phase += b.imag*log(vabs)
return complex(len*cos(phase), len*sin(phase)) |
…mbers Before, handling of numbers with special values in components (infinities, nans, signed zero) was invalid. Simple example: >>> z = complex(1, -0.0) >>> z*z (1-0j) >>> z**2 (1+0j) Now: >>> z**2 (1-0j)
See also #60200 (comment) example from SO (last case lacks OverflowError in the current main). |
The problem in the current algorithm for small integer degree is that it assumes that For So, |
Unfortunately, I doubt that binary exponentiation can be easily (if at all) fixed to address this. Another example from: >>> import _testcapi
>>> z = -1-1j
>>> _testcapi._py_c_pow(z, 6)[0]
(4.408728476930473e-15-8.000000000000004j)
>>> z**6
(-0-8j)
>>> z*z*z*z*z*z
-8j |
* Fix the sign of zero components in the result. E.g. complex(1,-0.0)**2 now evaluates to complex(1,-0.0) instead of complex(1,-0.0). * Fix negative small integer powers of infinite complex numbers. E.g. complex(inf)**-1 now evaluates to complex(0,-0.0) instead of complex(nan,nan). * Powers of infinite numbers no longer raise OverflowError. E.g. complex(inf)**1 now evaluates to complex(inf) and complex(inf)**0.5 now evaluates to complex(inf,nan).
See also #124243 which fixes calculation of small integer powers of complex numbers. It guarantees that |
For example,
"Big enough" or non-integer powers are unaffected:
It seems, this is related to
cpython/Objects/complexobject.c
Line 164 in c179c0e
Instead, imaginary component of
r
should copy the sign fromx.imag
.Linked PRs
The text was updated successfully, but these errors were encountered: