Skip to content
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

gh-117999: Fix small integer powers of complex numbers #124243

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented Sep 19, 2024

  • 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).

* 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).
@skirpichev
Copy link
Member

I'll take look, but probably already tomorrow :(

Meanwhile, I'm not sure if fix for division should be included here. That does make sense for a separate pr, that will provide mixed-mode arithmetic for complex.

I was thinking (In fact, I did, patch just lacks more tests: skirpichev#4) on splitting out from skirpichev#1 just cases for real op complex, as specified by C99+ (note, that there is no special case for real/complex per standard). That's something unfortunate, but imaginary numbers gone from latest drafts of upcoming C standard.

Copy link
Member

@skirpichev skirpichev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks close to #118000. I doubt there is an easy way to fix powi() algorithm to match generic one (same signs, etc).

Comment on lines 375 to 380
self.assertComplexesAreIdentical(c**0, complex(1, +0.0))
self.assertComplexesAreIdentical(c**1, c)
self.assertComplexesAreIdentical(c**2, c*c)
self.assertComplexesAreIdentical(c**3, c*(c*c))
self.assertComplexesAreIdentical(c**4, (c*c)*(c*c))
self.assertComplexesAreIdentical(c**5, c*((c*c)*(c*c)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather worry if lhs will not match not with some random arrangements of parens on rhs, but with the generic algorithm (i.e. _testcapi._py_c_pow()).

But, e.g.

>>> import ctypes, _testcapi
>>> libm = ctypes.CDLL('libm.so.6')
>>> libm.cpow.argtypes = [ctypes.c_double_complex, ctypes.c_double_complex]
>>> libm.cpow.restype = ctypes.c_double_complex
>>> z = -1-1j
>>> z**6
(-0-8j)
>>> _testcapi._py_c_pow(z, 6)
((4.408728476930473e-15-8.000000000000004j), 0)
>>> libm.cpow(z, 6)
(4.408728476930471e-15-7.999999999999998j)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added new tests to test that the result has zero component with the same sign as the result for small non-zero component.

Comment on lines +247 to +259
assert(n > 0);
while ((n & 1) == 0) {
x = _Py_c_prod(x, x);
n >>= 1;
}
Py_complex r = x;
n >>= 1;
while (n) {
x = _Py_c_prod(x, x);
if (n & 1) {
r = _Py_c_prod(r, x);
}
n >>= 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks longer than in #118000, but is there a difference (up to handling non-negative exponents in my case)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#118000 always has additional multiplication by c, even if n is the power of 2.

For example, if n=8, c**16 is calculated as:

Comment on lines +639 to +641
if (isfinite(a.real) && isfinite(a.imag)) {
_Py_ADJUST_ERANGE2(p.real, p.imag);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we don't call math functions in c_powi. Is this workaround needed at all?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this, complex(inf)**1 raises an OverflowError.

Comment on lines +235 to +239
if (isfinite(a.real) && isfinite(a.imag)
&& isfinite(b.real) && isfinite(b.imag))
{
_Py_ADJUST_ERANGE2(r.real, r.imag);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks correct for me, given description of _Py_ADJUST_ERANGE2(). But what we gain here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this, complex(inf)**101 raised an OverflowError.

There is no test for this, because the result ((inf+nanj)) is still incorrect -- it should be (inf+0j), as for smaller exponents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants