-
-
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 inv and div for Complex{Union{Float16, Float32}} #44111
faster inv and div for Complex{Union{Float16, Float32}} #44111
Conversation
Benchmarks look pretty good to me
|
Maybe I have missed something but isn't it a problem that CFloat32/16 semantic differs from CFloat64 ? (not the same checks). |
This isn't quite right yet, but a lot of the checks have to do with numeric issues that don't exist when you do the math in wider precision. |
d2fa15c
to
7869e8a
Compare
Can we please have correct functions rather than fast ones? julia> function Base.inv(z::Complex{<:Union{Float16,Float32}})
c, d = reim(widen(z))
(isinf(c) | isinf(d)) && return complex(copysign(zero(z), c), flipsign(-zero(z), d))
mag = inv(muladd(c, c, d^2))
return oftype(z, Complex(c*mag, -d*mag))
end
julia> inv(complex(Inf32))
ERROR: MethodError: no method matching copysign(::ComplexF32, ::Float64)
Closest candidates are:
copysign(::Signed, ::Float64) at /usr/share/julia/base/int.jl:149
copysign(::Signed, ::Real) at /usr/share/julia/base/int.jl:150
copysign(::Rational, ::Real) at /usr/share/julia/base/rational.jl:256
...
Stacktrace:
[1] inv(z::ComplexF32)
@ Main ./REPL[1]:3
[2] top-level scope
@ REPL[3]:1 |
spelling is hard. |
Relatedly, division of complex numbers has been plagued by several issues, some of them have been fixed over time, but for example #32992 still stands. Might be a good occasion to fix it? |
Can you write me a test script for what the behavior should be? I think I want to merge this without fixing the Float64 edge cases (1.8 feature freeze is coming up), but I'd be happy to fix these in a follow-up PR. |
@testset "Complex division by infinity - $T" for T in (Float16, Float32, Float64)
@test isequal(complex(one(T)) / complex(T(Inf), T(Inf)), complex(zero(T)))
end Note that with latest version of this PR julia> function Base.:/(z::Complex{T}, w::Complex{T}) where {T<:Union{Float16,Float32}}
c, d = reim(widen(w))
a, b = reim(widen(z))
(isinf(c) | isinf(d)) && return complex(copysign(zero(T), c), flipsign(zero(T), d)) * z
mag = inv(muladd(c, c, d^2))
re_part = muladd(a, c, b*d)
im_part = muladd(b, c, -a*d)
return oftype(z, Complex(re_part*mag, im_part*mag))
end
julia> function Base.inv(z::T) where T<:Complex{<:Union{Float16,Float32}}
c, d = reim(widen(z))
(isinf(c) | isinf(d)) && return complex(copysign(zero(T), c), flipsign(-zero(T), d))
mag = inv(muladd(c, c, d^2))
return oftype(z, Complex(c*mag, -d*mag))
end
julia> inv(complex(Inf32))
ERROR: MethodError: no method matching copysign(::ComplexF32, ::Float64)
Closest candidates are:
copysign(::Signed, ::Float64) at /usr/share/julia/base/int.jl:149
copysign(::Signed, ::Real) at /usr/share/julia/base/int.jl:150
copysign(::Rational, ::Real) at /usr/share/julia/base/rational.jl:256
...
Stacktrace:
[1] inv(z::ComplexF32)
@ Main ./REPL[5]:3
[2] top-level scope
@ REPL[6]:1 Same error as before |
@giordano how would you feel about changing the sign of the zeros for |
What you suggest makes sense, but in v1.7.2 I get: julia> big(1.0 + 0.0im)/big(-Inf + Inf*im) == inv(big(-Inf + Inf*im))
true so that's already the case? If in |
huh, I clearly need to experiment more... |
test/complex.jl
Outdated
@test isequal(complex(one(T)) / complex(T(Inf), T(-Inf)), complex(zero(T), zero(T))) broken=(T==Float64) | ||
@test isequal(complex(one(T)) / complex(T(-Inf), T(Inf)), complex(-zero(T), -zero(T))) broken=(T in (Float32, Float64)) | ||
@test isequal(complex(one(T)) / complex(T(Inf), T(-Inf)), complex(zero(T), zero(T))) | ||
@test isequal(complex(one(T)) / complex(T(-Inf), T(Inf)), complex(zero(T), -zero(T))) |
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.
Why the sign change?
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.
Because I was having trouble with math. Complex numbers in IEEE don't have a multiplicative identity. Complex(1.,0.)*Complex(-0.,-0.)==Complex(0.,-0.)
and Complex(1.,-0.)*Complex(-0.,-0.)==Complex(-0.,0.)
, so my idea for getting the signs wasn't working...
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.
do you know a good algorithm for computing the sign of the zeros when we know that the divisor is some type of infinity? I've looked for a bit, but can't come up with anything nice.
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.
I think it should be the following: in (a + b * im) / (c + d * im)
, assuming a
and b
finite and non-zero, and c
and d
infinite:
- real part:
sign(a) * sign(c)
- imaginary part:
sign(b) * -sign(d)
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.
For completeness, you should likely refer to the ISO/IEC 10967-3:2006 standard, section "5.2
Imaginary and complex floating point datatypes and operations"
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.
It's a bit worrying to me how e.g. signs in tests are being changed with an off-hand "fix tests" commit. It's to me a bit of a Chesterton's fence situation. A significant amount of work has gone in to making sure that these things are working the way they are and one needs to be quite careful when changing these pretty low-level operations.
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.
@giordano thanks for finding the thing that actually works.
71f90a2
to
1223c92
Compare
Is this ready to merge (assuming CI finally works?) |
Failures like
and
look relevant |
Ok, performance improvements look good and we're reconciling with math for division by infinity across all types. Failure on Linux looks unrelated as far as I could tell, but FreeBSD and macOS tests are stalling, I don't know what's their status nowadays. |
Ok. I think this is finally ready. |
As discovered in https://discourse.julialang.org/t/efficient-calculation-of-many-complex-lorentzians-on-a-large-array/76150/21, Complex division for
Float16
andFloat32
are currently doing a lot of extra work. This still does the math inFloat64
, but ignores all the extra checks.