gh-156695: Improve accuracy of complex powers with small negative integer exponents - #156757
gh-156695: Improve accuracy of complex powers with small negative integer exponents#156757Aniketsy wants to merge 4 commits into
Conversation
…ve integer exponents
|
The result is improving for the example of the OP, but some results are worse as well. E.g. |
skirpichev
left a comment
There was a problem hiding this comment.
Yes, unfortunately this is not an easy issue.
You could compare old/new results with correctly rounded powers (using e.g. GNU MPC) to see if the net impact is positive. Take look on https://inria.hal.science/hal-04714173 for inspiration.
…ve integer exponents
thanks for pointing out i dived into this, these are some results with these scripts i used Details
Details
yes it got trickier than i thought, and thanks for the reference paper |
|
With original patch I've this: Details# compare.py -- normwise error vs correctly rounded MPC results
import cmath, math, random, statistics
from gmpy2 import mpc, ieee, set_context
set_context(ieee(64))
def normwise(got, ref):
# Eq. (1) of Caprioli, Innocente & Zimmermann: e = |delta| / ulp(|z|)
if got is None:
return float('inf')
if cmath.isnan(got):
return
if cmath.isinf(got):
return
diff = abs(got - ref)
if not math.isfinite(diff):
return
return diff/math.ulp(abs(ref))
def measure(pow_fn, samples):
errs = []
for z, n in samples:
ref = complex(mpc(z) ** n)
try:
got = pow_fn(z, n)
except (OverflowError, ZeroDivisionError):
got = None
errs.append(normwise(got, ref))
excluded = sum(1 for e in errs if e is None)
scored = [e for e in errs if e is not None]
return (statistics.median(scored),
statistics.quantiles(scored, n=100)[94],
max(scored),
excluded,
errs)
random.seed(0)
samples = []
for _ in range(100000):
n = -random.choice((1, 2, 3, 5, 7, 12, 40, 100))
z = complex(random.uniform(-1, 1), random.uniform(-1, 1))
z *= math.ldexp(1.0, random.randint(-1020, 1020))
if z == 0 or not cmath.isfinite(z):
continue
ref = complex(mpc(z) ** n)
if ref == 0 or not math.isfinite(abs(ref)):
continue
samples.append((z, n))
med, p95, mx, bad, errs = measure(lambda z, n: z ** n, samples)
print(f"n={len(samples)} median={med:.3f} p95={p95:.3f} max={mx:.4g} excluded={bad}")
import json, sys
if len(sys.argv) > 1:
with open(sys.argv[1], "w") as f:
json.dump([e if (e is not None and math.isfinite(e)) else None for e in errs], f) |
| if (errno == EDOM | ||
| || (isfinite(r.real) && isfinite(r.imag) | ||
| && (r.real != 0.0 || r.imag != 0.0))) | ||
| return r; |
There was a problem hiding this comment.
Any example, that trigger that case?
| /* gh-156695: x**|n| left the exponent range although the result is | ||
| representable. Redo it with x scaled to exponent zero; both the | ||
| scaling and its undoing are exact. */ |
There was a problem hiding this comment.
You recompute power and quotient again, unconditionally. I believe it will introduce a severe speed regression.
Fixes #156695