Skip to content
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

conj(sqrt(complex(-5,0))) is not the same as sqrt(conj(complex(-5,0))) #2843

Closed
ViralBShah opened this issue Apr 12, 2013 · 20 comments
Closed
Labels
needs decision A decision on this change is needed

Comments

@ViralBShah
Copy link
Member

An example from page 15 of http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf

julia> conj(sqrt(complex(-5,-0)))
0.0 - 2.23606797749979im

julia> sqrt(conj(complex(-5,-0)))
0.0 + 2.23606797749979im

This is also the case with log:

julia> conj(log(complex(-5,0)))
1.6094379124341003 - 3.141592653589793im

julia> log(conj(complex(-5,0)))
1.6094379124341003 + 3.141592653589793im

Octave gives the same result in both cases.

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

This is not necessarily a bug, but rather the reflection of some choice of branch cut embedded in the complex sqrt function. It is easy to verify that the two different answers correspond to different square roots of -5.

julia>x= conj(sqrt(complex(-5,-0)))
0.0 - 2.23606797749979im

julia> conj(x^2)
-5.000000000000001 + 0.0im

julia> conj(x)^2
-5.000000000000001 + 0.0im

julia>x= sqrt(conj(complex(-5,-0)))
0.0 + 2.23606797749979im

julia> conj(x)^2
-5.000000000000001 - 0.0im

julia> conj(x^2)
-5.000000000000001 - 0.0im

@ViralBShah
Copy link
Member Author

According to Kahan: http://people.freebsd.org/~das/kahan86branch.pdf

@ViralBShah
Copy link
Member Author

Yes, they are different square roots of -5. I am not sure if the branch cut we have chosen is by design. I have changed the label to a decision rather than a bug.

@ViralBShah
Copy link
Member Author

Should conj not change the signbit of 0 in the second case here?

julia> conj(complex(5,-0))
5 + 0im

julia> conj(complex(5,0))
5 + 0im

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

Yes, that is clearly an error. The Kahan article blames this as the root cause, which would in effect determine the behavior at branch cuts.

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

FWIW the first link you provide clearly favors the ISO C99 standard. Here is what it has to say on this topic (emph. mine):

7.3.3 Branch cuts
1 Some of the functions below hav e branch cuts, across which the function is
discontinuous. For implementations with a signed zero (including all IEC 60559
implementations) that follow the specifications of annex G, the sign of zero distinguishes
one side of a cut from another so the function is continuous (except for format
limitations) as the cut is approached from either side. For example, for the square root
function, which has a branch cut along the negative real axis, the top of the cut, with
imaginary part +0, maps to the positive imaginary axis, and the bottom of the cut, with
imaginary part −0, maps to the negative imaginary axis.

2 Implementations that do not support a signed zero (see annex F) cannot distinguish the
sides of branch cuts. These implementations shall map a cut so the function is continuous
as the cut is approached coming around the finite endpoint of the cut in a counter
clockwise direction. (Branch cuts for the functions specified here have just one finite
endpoint.) For example, for the square root function, coming counter clockwise around
the finite endpoint of the cut along the negative real axis approaches the cut from above,
so the cut maps to the positive imaginary axis

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

julia> 0==-0
true

julia> bits(0)
"0000000000000000000000000000000000000000000000000000000000000000"

julia> bits(-0)
"0000000000000000000000000000000000000000000000000000000000000000"

julia> bits(-0.0e0)
"1000000000000000000000000000000000000000000000000000000000000000"

julia> bits(0.0e0)
"0000000000000000000000000000000000000000000000000000000000000000"

julia> 0.0e0 == -0.0e0
true

Erm.

@pao
Copy link
Member

pao commented Apr 12, 2013

Literal 0 is an Int. Isn't everything else here floating-point?

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

Oh, right, duh.

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

Ah, so part of my confusion is that complex(-5,-0) is equivalent to complex(-5, 0) since they are both Complex{Int64}. This however doesn't quite address the original branch cut issue:

julia> conj(complex(5.0, -0.0))
5.0 + 0.0im

julia> conj(complex(5.0, +0.0))
5.0 - 0.0im

julia> conj(sqrt(complex(-5,+0)))
0.0 - 2.23606797749979im

julia> conj(sqrt(complex(-5,-0)))
0.0 - 2.23606797749979im

julia> conj(sqrt(complex(-5.0e0,-0.0e0)))
0.0 - 2.23606797749979im

julia> sqrt(conj(complex(-5.0e0,-0.0e0)))
0.0 + 2.23606797749979im

julia> sqrt(conj(complex(-5.0e0,+0.0e0)))
0.0 + 2.23606797749979im

julia> conj(sqrt(complex(-5.0e0,+0.0e0)))
0.0 - 2.23606797749979im

@andreasnoack
Copy link
Member

There seem to two problems as @jiahao points out. There is a branch cut issue for the complex sqrt. log seems okay.

julia> conj(log(complex(-5.,-0.)))
1.6094379124341003 + 3.141592653589793im

julia> log(conj(complex(-5.,-0.)))
1.6094379124341003 + 3.141592653589793im

The second problems is how to deal with Complex{Int}. Maybe this is not so important because most often when you want to use sqrt or log the type is likely to be Complex{Float}.

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

This behavior makes sense then from a complex analysis standpoint. The Riemann surface of complex log is well-behaved except at complex 0, where the branch cuts serve to chop up the Riemann surface into single-valued domains, whereas the square root has nontrivial singularities at the branch cut which require a decision to be made about which surface to follow at the branch cut.

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

Going back and reading the ISO C99 standard, it seems clear to me that the behavior we see conforms to the branch cut decisions made in the standard. Complex{Float*} is in case 1 (with signed zeros) and Complex{Int*} is in case 2 (no signed zeros, use counterclockwise convention).

@simonbyrne
Copy link
Contributor

I don't think it is matching up to the C99 standard for floats:

julia> sqrt(complex(-1.0,-0.0))
0.0 + 1.0im

julia> sqrt(complex(-1.0,-1e-100))
5.0e-101 - 1.0im

@JeffBezanson
Copy link
Member

I think there is a comparison in there where there should be a copysign.

@JeffBezanson
Copy link
Member

Fixed in d3c66e9.

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

Currently in d3c66e9:

julia> sqrt(conj(complex(-5.0,-0.0)))
0.0 + 2.23606797749979im

julia> sqrt(conj(complex(-5.0,0.0)))
0.0 - 2.23606797749979im

julia> conj(sqrt(complex(-5,-0)))
0.0 - 2.23606797749979im

julia> conj(sqrt(complex(-5,0)))
0.0 - 2.23606797749979im

@StefanKarpinski
Copy link
Member

Is that good or bad? Note that since there's isn't a -0 integer, the last two will always have the same behavior.

@jiahao
Copy link
Member

jiahao commented Apr 12, 2013

The behavior @simonbyrne pointed out now appears to conform to the C99 standard.

Case 1: signed zeros, sqrt(x+0.0_im) is positive imaginary, sqrt(x-0.0_im) is negative imaginary.

julia> sqrt(complex(-1.0,+0.0))
0.0 + 1.0im

julia> sqrt(complex(-1.0,+1e-100))
5.0e-101 + 1.0im

julia> sqrt(complex(-1.0,-0.0))
0.0 - 1.0im

julia> sqrt(complex(-1.0,-1e-100))
5.0e-101 - 1.0im

Case 2: no signed zeros, sqrt(x+0*im) is positive imaginary.

julia> sqrt(complex(-1,0))
0.0 + 1.0im

Since sqrt(complex(-1,-0)) caused confusion above, this behavior may be a good thing to highlight in the documentation to clarify how Complex{Int} works.

@ViralBShah
Copy link
Member Author

I missed all the action here after filing. With #2845, we should hopefully be able to visit all the branch cuts and be compliant with C99.

One less reason for William Kahan to be unhappy with us now. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs decision A decision on this change is needed
Projects
None yet
Development

No branches or pull requests

7 participants