-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Should complex numbers be closed at infinity by default? #9790
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
Comments
Probably related: #5234 |
There are two issues. The first is entirely a duplicate of #5234. The second is not a problem with how complex numbers are implemented, but rather a question of rather we want complex numbers to be closed at infinity by default. |
It is also good to cross-reference relevant threads on the mailing list. Ref: julia-users. |
I've posted an idea for how to handle here: |
I think that both of the following are implementation bugs, and should be fixed: julia> 1/(Inf + Inf*im)
NaN + NaN*im
julia> 1. / (0.+0.im)
NaN + NaN*im From the C 2011 standard (with which we are generally trying to be compatible):
@scheinerman I'm still not really convinced of the advantages of a single complex infinity: do you have a particular application in mind for which this would be better than the current behaviour? |
Hi Simon, It's a fair question: do I have an application in mind? Very short answer: No. My perspective is that of a mathematician and how we might extend the (finite) complex numbers to include infinity. For the real numbers, extending with separate positive and negative infinities is fine. Having those two, and only those two, infinities for the complex numbers doesn't make sense. This leads us to a few reasonable choices. But for whatever we decide, having A single But if we wish to emulate I also think that having a single But I concede this: Efficiency and speed may well trump mathematical elegance. Having to do checks for every complex operation will slow things down. Folks that just want to do a bunch of complex arithmetic quickly in which infinities and/or NaN's are not going to crop up would not want this overhead. Hence my proposed solution is to create a package that a user may load that redefines |
There are probably arguments to be made either way. The Riemann sphere is very elegant, but treating complex numbers as a Cartesian pair of real numbers is also elegant. I favour consistency between languages. Since e.g. C, C++, or maybe even Lisp define how infinities and nans should behave, we should follow suit. If we want to diverge, then this should be a large community-wide decision, and we may even want comments from the C and C++ standards committees to see why they decided their way. The argument regarding "don't care about infinities and nans" is not valid. The IEEE standard defines very closely how these are handled, and by default, Julia follows this standard. We should attempt something equivalent for complex numbers, and not say "efficiency trumps everything for complex numbers". For example, there's always |
In the ==(z::Complex, w::Complex) = (real(z) == real(w)) & (imag(z) == imag(w)) #Argand plane
==(z::Complex, w::Complex) = if isinf(z) && isinf(w)
return true #Riemann closure at infinity
else
return (real(z) == real(w)) & (imag(z) == imag(w))
end It might seem a little crazy, but redefining equality is actually not an unreasonable way to capture the notion of topological closure. This option just happens to be not usually possible in most languages. |
Hmm, this is an intriguing idea: the main downside would be the overhead of the branch. The C standard seems fairly flexible when it comes to complex infinities: as above, it uses the phrase "an infinity" and "a zero" a lot. In case anyone is wondering, the C standard (like Julia) regards Any discussion of complex infinities cannot go without a reference to Kahan's article "Branch Cuts for Complex Elementary Functions or Much Ado About Nothing's Sign Bit". We should probably also try to conform to ISO/IEC 10967-3:2006 "Language independent arithmetic -- Part 3: Complex integer and floating point arithmetic and complex elementary numerical functions" (free link from here), which specifies "general" behaviour of complex numbers in software (though it doesn't seem to say too much about infinities). |
@simonbyrne The language in the ISO standard mirrors much of the C99 spec. I found the C9X N557 draft more enlightening, as it was annotated with comments and discussions that were omitted from the final spec. |
Yes, I've looked briefly through other LIA specs and generally they are not all that enlightening, as they seem to be written so that almost all existing software would be conformant. |
Hello again folks. First, I was curious to see how C++ handles complex division by zero. So I wrote this:
and the output when run on my Mac is this
but when run on my Ubuntu linux machine I get something slightly different:
The negative So the fact that Julia evaluates Now Jiahao's idea of redefining julia> z = (1+0im)/0
Inf + NaN*im
julia> isinf(z)
true
julia> 1/z
NaN + NaN*im By the way, this is something C++ gets right. Dividing |
Re-defining equality to close complex numbers at infinity as one disadvantage:
That is, numbers that compare unequal as Float then suddenly compare equal when converted to Complex. |
Yup. We can’t have everything. One might argue (I won’t) that there should only be a single inf for float values too (one-point closure of the real number system). Thinking about this more over the day leads me to think we should leave this to user preference. Provide a default that conforms to C++ or some other good standard, but then people can load a module to redefine the behavior to their preference/application. By the way, when I redefine (say) + for Complex numbers, I get a warning that I’ve redefined + for Complex numbers … anyway to suppress that?
|
There is no reason to expect that the real number line can be closed the same way as the complex numbers can. Agreed that closure should be user opt-in behavior. |
Unfortunately it looks like division by complex zero has to be special cased; none of the commonly used algorithms compute the complex infinity correctly. (The last two are taken directly from the base library.) #Naive
function naive(z::Complex, w::Complex)
a, b = reim(z)
c, d = reim(w)
den = c*c + d*d
Complex( (a*c + b*d)/den, (b*c - a*d)/den)
end
#Smith, 1962
function smith(z::Complex, w::Complex)
a₀, b₀ = reim(z)
a₁, b₁ = reim(w)
if abs(a₁) ≥ abs(b₁)
c = b₁/a₁
d = a₁ + b₁*c
Complex((a₀ + b₀*c)/d, (b₀ - a₀*c)/d)
else
c = a₁/b₁
d = a₁*c + b₁
Complex((a₀*c + b₀)/d, (b₀*c - a₀)/d)
end
end
#Priest, 2004
function priest{T<:Real}(z::Complex{T}, w::Complex{T})
a₀, b₀ = reim(z)
a₁, b₁ = reim(w)
s = (a₁*a₁ + b₁*b₁)^(-1.5) #Optimal choice; in practice, scale factor is chosen iteratively
a₁′ = s * a₁
b₁′ = s * b₁
t = 1/(a₁′*a₁′ + b₁′*b₁′)
a₁′′ = s * a₁′
b₁′′ = s * b₁′
x = (a₀ * a₁′′ + b₀ * b₁′′ ) * t
y = (b₀ * a₁′′ - a₀ * b₁′′ ) * t
Complex(x, y)
end
#Kahan
function kahan{T<:Real}(a::Complex{T}, b::Complex{T})
are, aim = reim(a)
bre, bim = reim(b)
if abs(bre) <= abs(bim)
if isinf(bre) && isinf(bim)
r = sign(bre)/sign(bim)
else
r = bre / bim
end
den = bim + r*bre
Complex((are*r + aim)/den, (aim*r - are)/den)
else
if isinf(bre) && isinf(bim)
r = sign(bim)/sign(bre)
else
r = bim / bre
end
den = bre + r*bim
Complex((are + aim*r)/den, (aim - are*r)/den)
end
end
# Baudin, 2012
# arXiv:1210.4539
function baudin{T<:Real}(z::Complex{T}, w::Complex{T})
a, b = reim(z); c, d = reim(w)
half = 0.5
two = 2.0
ab = max(abs(a), abs(b))
cd = max(abs(c), abs(d))
ov = realmax(a)
un = realmin(a)
ϵ = eps(T)
bs = two/(ϵ*ϵ)
s = 1.0
ab >= half*ov && (a=half*a; b=half*b; s=two*s ) # scale down a,b
cd >= half*ov && (c=half*c; d=half*d; s=s*half) # scale down c,d
ab <= un*two/ϵ && (a=a*bs; b=b*bs; s=s/bs ) # scale up a,b
cd <= un*two/ϵ && (c=c*bs; d=d*bs; s=s*bs ) # scale up c,d
abs(d)<=abs(c) ? ((p,q)=robust_cdiv1(a,b,c,d) ) : ((p,q)=robust_cdiv1(b,a,d,c); q=-q)
return Complex(p*s,q*s) # undo scaling
end
function robust_cdiv1{T<:Real}(a::T, b::T, c::T, d::T)
r = d/c
t = 1.0/(c+d*r)
p = robust_cdiv2(a,b,c,d,r,t)
q = robust_cdiv2(b,-a,c,d,r,t)
return p,q
end
function robust_cdiv2{T<:Real}(a::T, b::T, c::T, d::T, r::T, t::T)
if r != 0
br = b*r
return (br != 0 ? (a+br)*t : a*t + (b*t)*r)
else
return (a + d*(b/c)) * t
end
end
@show naive(Complex(1.0, 0.0), Complex(0.0, 0.0)) #NaN + NaN*im
@show smith(Complex(1.0, 0.0), Complex(0.0, 0.0)) #NaN + NaN*im
@show priest(Complex(1.0, 0.0), Complex(0.0, 0.0)) #NaN + NaN*im
@show kahan(Complex(1.0, 0.0), Complex(0.0, 0.0)) #NaN + NaN*im
@show baudin(Complex(1.0, 0.0), Complex(0.0, 0.0)) #NaN + NaN*im |
Yes, we would need an explicit branch: there is some sample code for complex division in the C 1999 and 2011 standards (they're slightly different), and they both include an explicit branch to check for a zero denominator, or either argument an infinity. |
@scheinerman: When you redefine |
@toivoh : Thanks. Actually, that's exactly what I had in mind. A user who wants to work with alternative methods for So that's what I'm trying to do (though I'm not a pro!) with my julia> X = Set([1,2,3])
Set{Int64}({2,3,1})
julia> using ShowSet
Warning: Method definition show(IO,Set{T}) in module Base at set.jl:10 overwritten in module ShowSet at /Users/ers/.julia/v0.3/ShowSet/src/ShowSet.jl:25.
Warning: Method definition show(IO,IntSet) in module Base at intset.jl:176 overwritten in module ShowSet at /Users/ers/.julia/v0.3/ShowSet/src/ShowSet.jl:26.
julia> X
{1,2,3} I wonder if there's a way to (temporarily) suppress those warnings. |
Instead of redefining complex arithmetic, could you redefine the complex number type? I'm thinking of Overwriting a routine is global. (I assume this is what the warning is about.) If you then call a library that was designed with the "other" kind of complex numbers, things may break. If you instead overwrite things only in your module, everything should be safe. |
Complex division is already so slow that I wouldn't worry about adding operations to it. |
What happens if the infinities and NaNs occur as part of a linear algebra computation that is presumably rendered in a Blas or Lapack library? Will you end up with inconsistencies? From my point of view if all other matters are tied performance should win. After all performance is one of the great notable features of Julia. |
I'm not sure what happens when a Blas or Lapack function is called ... can you point me to some standard ones that I can try out? As to the performance issue, I think the resolution is to put the choice in the user's hands. If we're sure that infinities and NaN's won't occur, then speed wins the day. But I also think being correct is important. Consider this julia> z = (1+0im)/0
Inf + NaN*im
julia> isnan(z)
true I would argue this is just plain wrong. After julia> z = (1+0im)/0
ComplexInf
julia> isnan(z)
false and that's correct. |
If this hasn't had any discussion or activity in 16 months, I don't think it's release-blocking. |
I agree that performance top priority, but we have this inconsistency in division by zero:
Perhaps that can be fixed in a way that doesn't impact performance? |
This is certainly not a release blocker at this point. We've worked very hard to make our |
"But for whatever we decide, having Inf+2im and Inf+3im as different values is not reasonable" I only read this far. I was actually looking into implementing Complex with polar coordinates. There are Pros and cons (maybe I get around the cons..). At least your issue is then non-existent? |
Yes. There are two natural ways to complete the complex numbers either with a single "Complex Infinity" (my preference) or a "line at infinity" (having a different point at infinity for each ray or line out of the origin). In polar coordinates, then both Inf+2im and Inf+3im would have r=Inf and theta=0, and therefore be the same value. |
I don't think we're going to change from our current semantics for infinite complex values. The reals are embedded in the complexes as far as equality is concerned – as the values for which the imaginary part is zero. Some of the corner cases should probably be fixed, but we should have individual issues for those. |
The only issue here is division by zero, which could be argued to be a bug and/or undefined. |
The mixed type division should be adjusted to be in line with complex-complex division. |
Dividing a nonzero value by 0 should give an infinite result. This works fine for floating point numbers, but not for complex:
Similarly, dividing by infinity (with a finite numerator) should give 0. This isn't working entirely properly:
Finally, in my opinion, there should be only a single
ComplexInf
value and not allow this:Both
a
andb
should have evaluated to a commonComplexInf
value. (We may also need aComplexNaN
.) Separate+Inf
and-Inf
is fine forReal
values.The text was updated successfully, but these errors were encountered: