-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Faster, more correct complex^complex #24570
Conversation
Any chance of getting nice error plots to make sure that error stays below x ulps? |
function _cpow(z::Union{T,Complex{T}}, p::Union{T,Complex{T}}) where {T<:AbstractFloat} | ||
if isreal(p) | ||
pᵣ = real(p) | ||
if isinteger(pᵣ) && abs(pᵣ) < typemax(Int32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe write it as exp2(31)
instead? It is jarring to see integer types in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But typemax(Int32) is literally what we want to prevent overflow on 32-bit machines, and to have consistent behavior on 64-bit. The whole point of this if
statement is to convert to an integer type.
@oscardssmith, some plots of the old and new relative errors for The main trick is to get the edge cases (especially the sign of zero imaginary parts) correct, especially when you want to take advantage of real |
base/complex.jl
Outdated
end | ||
end | ||
elseif isreal(z) | ||
iszero(z) && return real(p) > 0 ? complex(z) : Complex(T(NaN),T(NaN)) # 0 or NaN+NaN*im |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@StefanKarpinski, is the policy these days to throw DomainError
rather than constructing NaN
explicitly, if it is practical to do so? inv(0.0+0.0im)
(and hence (0.0+0.0im)^-1
) doesn't throw an error, but I guess that is because it would slow it down too much to check for this?
I'm a little confused because it doesn't seem like #5234 was ever resolved.
What are the white points in the plots? |
@giordano, the white points are where the error is "zero" (i.e. the result, by luck, is exactly rounded), so log of the error is –∞. Note that the error is unlikely to be actually zero for the white points, it is just < 1ulp, so probably they should be bluish or greenish. Here are the plots re-done with the true error (i.e. I compute the error and then round from BigFloat to Float64 rather than the other way around), making sure the color scales match: |
Uhm, to me the current implementation seems to have more white points, on the other hand the proposed one looks more blueish, which is good anyway. |
If I change this to throw exceptions rather than creating NaNs from non-NaN inputs, that will technically be a breaking change. Again, I'm not sure what the desired behavior is these days? |
By the way, one argument against throwing Also, Are we okay with throwing
|
After sleeping on it, I've decided to revert the breaking change of throwing
|
We don't have a general rule that getting NaNs from non-NaN inputs should be a DomainError. That pattern was introduced just for a couple functions where that check happens to correctly identify DomainError cases. |
CI looks good except AppVeyor, where the error |
Just to be sure does this mean that you are converting the Float64 result to BigFloat, subtract a reference BigFloat result, divide by (?), and convert back to Float64? |
Essentially yes. To compute the relative error for the |
Should be good to merge? |
@nanosoldier |
Is nanosoldier working? It's been "running on node 3" for a day now. |
The only thing we can conclude is that this PR brings some serious performance regressions ;) |
Bump. Seems like there is no point in running nanosoldier right now... |
@nanosoldier |
Looks like nanosoldier is not working; seems like an unrelated |
Okay, not sure yet what the deal is. In the meantime you can benchmark manually with BenchmarkTools, since the macros work on 0.7 again now. |
Let's see if nanosoldier works now: @nanosoldier (Though it has been so noisy recently, even when it works, that the results have been hard to make use of.) |
@ararslan, what's up with nanosoldier? |
It's been acting up a bit lately. I think in this case I restarted the server after you had triggered a run, which means that the status for that run didn't get updated. I'll try retriggering. @nanosoldier |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan |
The nanosoldier regressions look like noise. (It doesn't look like there are any benchmarks in BaseBenchmarks that exercise complex powers.) |
Seems ready to merge? |
Yay, CI is green. |
This PR fixes the incorrect behaviors for
complex^complex
identified in #24515, and it also makes the code significantly faster without (as far as I can tell) sacrificing accuracy. On my machine, it is around 60% faster forcomplex^complex
and 120% faster forreal^complex
in double precision.The old code had two completely separate implementations, one for floating-point types and one for other types, despite the fact that both produced floating-point results. The floating-point version was based on
z^p = exp(p * log(z))
, whereas the other version first convertedz
to polar form and then exponentiated. The latter approach seems to be significantly faster and no less accurate, so I now use that in all cases (with various special-case optimizations for real z and/or p). By unifying the implementations, the code is also significantly shorter.Marked as breaking only because this throws exceptions in some cases where the old code would have silently returned NaNs.Returns NaN as before.See also the discussions in #2891, #3246, 06530b6.
To do:
DomainError
for(0.0+0.0im)^-1.0
and other cases ala NaN vs wild (or, what's a DomainError, really?) #5234