gh-117999: fixed small integer powers of complex numbers - #118000
gh-117999: fixed small integer powers of complex numbers#118000skirpichev wants to merge 16 commits into
Conversation
2a6babf to
5ee43ee
Compare
51c6cad to
cd3e11e
Compare
…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)
58c2fb1 to
8e0c482
Compare
|
@picnixz, I would appreciate your review on this pr. Or your opinion in the issue thread. |
|
I'll do it tomorrow! (Monday, Paris time) |
|
I need to think a bit more on the issue. I'll try to have something by the end of the day or tomorrow. Ideally, I would like to have no inconsistency between the generic algorithm and the non-generic one (namely, the result should be as if we were using the generic algorithm). |
I'm not sure if it's possible without too much code, that affects performance severely. BTW, I think that numpy code has no such special version for integer exponents. I'll double check. Edit: Ah, no. numpy mimics CPython here, at least in npy_math_complex.c.src. Edit2: JFR, some simple benchmarks. With specialized code (main): Without: |
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
|
This PR is stale because it has been open for 30 days with no activity. |
|
@picnixz, this is ready for review again. See #117999 (comment) - we still can't avoid differences wrt generic algorithm for all values. But probably it's fine. |
|
I run on this following script, comparing pow() output wrt GNU MPC. This PR (and #124243 as well) introduce additional differences (32 broken test): --- 1 2026-08-20 03:33:59.886826488 +0300
+++ 2 2026-08-20 03:58:01.747376340 +0300
@@ -6,6 +6,8 @@
(-inf-infj) (-0.5+nanj) 0j (nan+nanj)
(-inf-infj) (-0-0j) (1-0j) (1+0j)
(-inf-infj) -0j (1-0j) (1+0j)
+(-inf-infj) (0.5-0j) (inf+nanj) (inf-infj)
+(-inf-infj) (0.5+0j) (inf+nanj) (inf-infj)
(-inf-infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(-inf-infj) (inf+0.5j) (inf+nanj) (nan+nanj)
(-inf-infj) (inf+infj) (inf+nanj) (nan+nanj)
@@ -16,6 +18,8 @@
(-inf-0.5j) (-0.5+nanj) 0j (nan+nanj)
(-inf-0.5j) (-0-0j) (1-0j) (1+0j)
(-inf-0.5j) -0j (1-0j) (1+0j)
+(-inf-0.5j) (0.5-0j) (inf+nanj) (inf-infj)
+(-inf-0.5j) (0.5+0j) (inf+nanj) (inf-infj)
(-inf-0.5j) (0.5+nanj) (inf+nanj) (nan+nanj)
(-inf-0.5j) (inf+0.5j) (inf+nanj) (nan+nanj)
(-inf-0.5j) (inf+infj) (inf+nanj) (nan+nanj)
@@ -26,6 +30,8 @@
(-inf-0j) (-0.5+nanj) 0j (nan+nanj)
(-inf-0j) (-0-0j) (1-0j) (1+0j)
(-inf-0j) -0j (1-0j) (1+0j)
+(-inf-0j) (0.5-0j) (inf+nanj) (inf-infj)
+(-inf-0j) (0.5+0j) (inf+nanj) (inf-infj)
(-inf-0j) (0.5+nanj) (inf+nanj) (nan+nanj)
(-inf-0j) (inf+0.5j) (inf+nanj) (nan+nanj)
(-inf-0j) (inf+infj) (inf+nanj) (nan+nanj)
@@ -38,6 +44,8 @@
(-inf+0j) (-0.5+nanj) 0j (nan+nanj)
(-inf+0j) (-0-0j) (1-0j) (1+0j)
(-inf+0j) -0j (1-0j) (1+0j)
+(-inf+0j) (0.5-0j) (inf+nanj) (inf+infj)
+(-inf+0j) (0.5+0j) (inf+nanj) (inf+infj)
(-inf+0j) (0.5+nanj) (inf+nanj) (nan+nanj)
(-inf+0j) (inf-infj) (inf+nanj) (nan+nanj)
(-inf+0j) (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -50,6 +58,8 @@
(-inf+0.5j) (-0.5+nanj) 0j (nan+nanj)
(-inf+0.5j) (-0-0j) (1-0j) (1+0j)
(-inf+0.5j) -0j (1-0j) (1+0j)
+(-inf+0.5j) (0.5-0j) (inf+nanj) (inf+infj)
+(-inf+0.5j) (0.5+0j) (inf+nanj) (inf+infj)
(-inf+0.5j) (0.5+nanj) (inf+nanj) (nan+nanj)
(-inf+0.5j) (inf-infj) (inf+nanj) (nan+nanj)
(-inf+0.5j) (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -62,6 +72,8 @@
(-inf+infj) (-0.5+nanj) 0j (nan+nanj)
(-inf+infj) (-0-0j) (1-0j) (1+0j)
(-inf+infj) -0j (1-0j) (1+0j)
+(-inf+infj) (0.5-0j) (inf+nanj) (inf+infj)
+(-inf+infj) (0.5+0j) (inf+nanj) (inf+infj)
(-inf+infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(-inf+infj) (inf-infj) (inf+nanj) (nan+nanj)
(-inf+infj) (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -98,6 +110,8 @@
(-0.5-infj) (-0.5+nanj) 0j (nan+nanj)
(-0.5-infj) (-0-0j) (1-0j) (1+0j)
(-0.5-infj) -0j (1-0j) (1+0j)
+(-0.5-infj) (0.5-0j) (inf+nanj) (inf-infj)
+(-0.5-infj) (0.5+0j) (inf+nanj) (inf-infj)
(-0.5-infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(-0.5-infj) (inf+0.5j) (inf+nanj) (nan+nanj)
(-0.5-infj) (inf+infj) (inf+nanj) (nan+nanj)
@@ -142,6 +156,8 @@
(-0.5+infj) (-0.5+nanj) 0j (nan+nanj)
(-0.5+infj) (-0-0j) (1-0j) (1+0j)
(-0.5+infj) -0j (1-0j) (1+0j)
+(-0.5+infj) (0.5-0j) (inf+nanj) (inf+infj)
+(-0.5+infj) (0.5+0j) (inf+nanj) (inf+infj)
(-0.5+infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(-0.5+infj) (inf-infj) (inf+nanj) (nan+nanj)
(-0.5+infj) (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -154,6 +170,8 @@
(-0-infj) (-0.5+nanj) 0j (nan+nanj)
(-0-infj) (-0-0j) (1-0j) (1+0j)
(-0-infj) -0j (1-0j) (1+0j)
+(-0-infj) (0.5-0j) (inf+nanj) (inf-infj)
+(-0-infj) (0.5+0j) (inf+nanj) (inf-infj)
(-0-infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(-0-infj) (inf+0.5j) (inf+nanj) (nan+nanj)
(-0-infj) (inf+infj) (inf+nanj) (nan+nanj)
@@ -186,6 +204,8 @@
(-0+infj) (-0.5+nanj) 0j (nan+nanj)
(-0+infj) (-0-0j) (1-0j) (1+0j)
(-0+infj) -0j (1-0j) (1+0j)
+(-0+infj) (0.5-0j) (inf+nanj) (inf+infj)
+(-0+infj) (0.5+0j) (inf+nanj) (inf+infj)
(-0+infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(-0+infj) (inf-infj) (inf+nanj) (nan+nanj)
(-0+infj) (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -198,6 +218,8 @@
-infj (-0.5+nanj) 0j (nan+nanj)
-infj (-0-0j) (1-0j) (1+0j)
-infj -0j (1-0j) (1+0j)
+-infj (0.5-0j) (inf+nanj) (inf-infj)
+-infj (0.5+0j) (inf+nanj) (inf-infj)
-infj (0.5+nanj) (inf+nanj) (nan+nanj)
-infj (inf+0.5j) (inf+nanj) (nan+nanj)
-infj (inf+infj) (inf+nanj) (nan+nanj)
@@ -230,6 +252,8 @@
infj (-0.5+nanj) 0j (nan+nanj)
infj (-0-0j) (1-0j) (1+0j)
infj -0j (1-0j) (1+0j)
+infj (0.5-0j) (inf+nanj) (inf+infj)
+infj (0.5+0j) (inf+nanj) (inf+infj)
infj (0.5+nanj) (inf+nanj) (nan+nanj)
infj (inf-infj) (inf+nanj) (nan+nanj)
infj (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -242,6 +266,8 @@
(0.5-infj) (-0.5+nanj) 0j (nan+nanj)
(0.5-infj) (-0-0j) (1-0j) (1+0j)
(0.5-infj) -0j (1-0j) (1+0j)
+(0.5-infj) (0.5-0j) (inf+nanj) (inf-infj)
+(0.5-infj) (0.5+0j) (inf+nanj) (inf-infj)
(0.5-infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(0.5-infj) (inf+0.5j) (inf+nanj) (nan+nanj)
(0.5-infj) (inf+infj) (inf+nanj) (nan+nanj)
@@ -304,6 +330,8 @@
(0.5+infj) (-0.5+nanj) 0j (nan+nanj)
(0.5+infj) (-0-0j) (1-0j) (1+0j)
(0.5+infj) -0j (1-0j) (1+0j)
+(0.5+infj) (0.5-0j) (inf+nanj) (inf+infj)
+(0.5+infj) (0.5+0j) (inf+nanj) (inf+infj)
(0.5+infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(0.5+infj) (inf-infj) (inf+nanj) (nan+nanj)
(0.5+infj) (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -316,6 +344,8 @@
(inf-infj) (-0.5+nanj) 0j (nan+nanj)
(inf-infj) (-0-0j) (1-0j) (1+0j)
(inf-infj) -0j (1-0j) (1+0j)
+(inf-infj) (0.5-0j) (inf+nanj) (inf-infj)
+(inf-infj) (0.5+0j) (inf+nanj) (inf-infj)
(inf-infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(inf-infj) (inf+0.5j) (inf+nanj) (nan+nanj)
(inf-infj) (inf+infj) (inf+nanj) (nan+nanj)
@@ -404,6 +434,8 @@
(inf+infj) (-0.5+nanj) 0j (nan+nanj)
(inf+infj) (-0-0j) (1-0j) (1+0j)
(inf+infj) -0j (1-0j) (1+0j)
+(inf+infj) (0.5-0j) (inf+nanj) (inf+infj)
+(inf+infj) (0.5+0j) (inf+nanj) (inf+infj)
(inf+infj) (0.5+nanj) (inf+nanj) (nan+nanj)
(inf+infj) (inf-infj) (inf+nanj) (nan+nanj)
(inf+infj) (inf-0.5j) (inf+nanj) (nan+nanj)
@@ -497,4 +529,4 @@
(nan+nanj) (-0-0j) (1-0j) (1+0j)
(nan+nanj) (-0+0j) (1-0j) (1+0j)
====================================================
-Failures: 496 (21%)
+Failures: 528 (22%)I think it's fine, assuming that on above examples output must match |
hpkfft
left a comment
There was a problem hiding this comment.
This PR looks good to me, and I would approve it if I had the necessary access to do so.
Without this PR, (inf+0j)**(1+0j) raises an OverflowError, which I think is broken. It's bad to raise an error, since it can crash an entire Python program if the programmer is not expecting it to be possible. Note that for the real-valued case:
>>> math.inf**1
inf
| continue | ||
| for n in range(1, 9): | ||
| with self.subTest(exponent=-n): | ||
| self.assertComplexesAreIdentical(c**-n, 1/(c**n)) |
There was a problem hiding this comment.
This assertion is an implementation detail. In the future, for example, one might want to use the C math library's cpow for both positive and negative small integers, in which case the two expressions may not be identical.
If this test is seen as documenting an eternal, unbreakable contract with users, then I don't like it. On the other hand, the understanding might be that Python can change the implementation and then simply change the test as well. I'm perfectly fine with that. (I'm not sure of the development culture here.)
I guess the comment about identical also applies to c**2 == c*c and maybe even c**1 == c. In MPC, (inf+1j)**(1+0j) gives (inf+nanj)....
Perhaps, we should factor out this fix, as it looks less controversial. Problem noticed e.g. here: #60200 (comment) |
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) >>> complex('inf')**-1 0jNow:
>>> z**2 (1-0j) >>> complex('inf')**-1 -0jAlso, positive powers of infinite complex not raise anymore OverflowError's:
>>> complex('inf')**1 (inf+0j) >>> complex('inf')**0.5 (inf+nanj)