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

Sign of real part of -1.0im is negative #10000

Open
jiahao opened this issue Feb 1, 2015 · 14 comments
Open

Sign of real part of -1.0im is negative #10000

jiahao opened this issue Feb 1, 2015 · 14 comments
Labels
complex Complex numbers

Comments

@jiahao
Copy link
Member

jiahao commented Feb 1, 2015

This doesn't look right.

julia> -1.0*im
-0.0 - 1.0im

julia> 0.0-1.0im
0.0 - 1.0im
@JeffBezanson
Copy link
Member

Well, im isn't a pure imaginary type any more.

@jiahao
Copy link
Member Author

jiahao commented Feb 1, 2015

That's precisely the problem.

@JeffBezanson
Copy link
Member

We've been around that block before: #5468

@jiahao
Copy link
Member Author

jiahao commented Feb 1, 2015

Agreed, but I feel that it's worth reconsidering that decision, or least looking more carefully at how we do mixed real-imaginary and real-complex arithmetic. There have been a few issues with complex arithmetic recently like #9531 and #9790, and pure imaginary literals like -1.0im are quite common.

@jiahao
Copy link
Member Author

jiahao commented Feb 1, 2015

The reason why this particular case is so important and problematic is that Complex(0.0, -1.0) and Complex(-0.0, -1.0) are in different quadrants of the complex plane. One can get unexpected results with functions with branch cuts along the negative imaginary axis.

julia> f(z)=log(-im*z); f(-1.0im)
0.0 + 3.141592653589793im

julia> f(0.0-1.0im)
0.0 - 3.141592653589793im

julia> f(-0.0-1.0im)
0.0 + 3.141592653589793im

julia> asinh(-2.0im)
-1.3169578969248166 - 1.5707963267948966im

julia> asinh(0.0-2.0im)
1.3169578969248166 - 1.5707963267948966im

julia> atan(0.0-2.0im)
1.5707963267948966 - 0.5493061443340549im

julia> atan(-2.0im)
-1.5707963267948966 - 0.5493061443340549im

In these situations, the sign of zero matters greatly.

@jiahao jiahao added the complex Complex numbers label Feb 1, 2015
@JeffBezanson
Copy link
Member

Maybe we should have -1.0 * false == 0.0 ?

@eschnett
Copy link
Contributor

eschnett commented Feb 1, 2015

The issue seems to be that the two operations "convert imaginary to complex" and "negate" commute for integer types, but do not commute for IEEE numbers. To make -1.0im give the expected result, one needs to first negate, then convert to complex.

-1.0 * false == 0.0 runs counter to the usual type-promotion scheme in Julia. Introducing special cases for arithmetic with Bool seems like band-aid. That is, it's difficult to ensure that this doesn't lead to strange or unexpected behaviour in other cases.

I think people find the value of -1.0im surprising (I do as well) because, in our minds, there is something very similar to the notion of an "imaginary type": That is, a type Imaginary{T} that supports addition like an underlying real type T:

+(x::Imaginary64, y::Imaginary64) = x+y :: Imaginary64
*(x::Imaginary64, y::Float64) = x*y :: Imaginary64
*(x::Imaginary64, y::Imaginary64) = - x*y :: Float64
# of course:
complex128(x::Imaginary64) = complex128(zero(x), x)
# but also:
exp(x::Imaginary64) = (optimized-implementation)

(Of course, the implementations above are a sketch only.)

I like the very elegant notation of an optimized exp(im*phi) that comes out quite naturally.

@ViralBShah
Copy link
Member

That is a landmark issue number!

@jiahao
Copy link
Member Author

jiahao commented Feb 1, 2015

@eschnett The problem is in complex multiplication, not in the order of negation vs multiplication:

julia> (-1.0)im
-0.0 - 1.0im

julia> -(1.0im)
-0.0 - 1.0im

Because im is defined as Complex(false, true), the first expression evaluates to Complex(-1.0*false, -1.0*true) == Complex(-0.0, -1.0). 0.0-1.0im works because it is evaluated as the subtraction operation.

@JeffBezanson's proposal to change -1.0*false == 0.0 would fix this specific problem, but I do wonder what else may break. I think mixed float-integer arithmetic is not something that is very well-studied.

@eschnett's proposal to have a separate imaginary type echoes Kahan's 1991 proposal. I think we have tried to follow the spirit of the proposal as much as possible without introducing Imaginary types that are analogous to the Reals,.

@eschnett
Copy link
Contributor

eschnett commented Feb 1, 2015

@jiahao I spoke of the two operations "convert to complex" (Complex128) and "negate" that don't commute, not multiplication and negation... For example, if you had im of type Complex{Int}, then you could negate it before converting to Complex128, and things would work out fine. (Of course, this wouldn't mix with the syntax -1.0im at all; I'm just trying to make the distinction between "negation" and "convert to Complex128" here.)

@jiahao
Copy link
Member Author

jiahao commented Feb 1, 2015

Ah, I didn't get the first time that you meant negation of non-floats. Yes that would also be true.

@eschnett
Copy link
Contributor

Thinking about this again, after a long time: The expression -im should probably be interpreted as Complex(-1.0,0.0) * Complex(0.0,1.0), which yields -0.0 - 1.0im.

We should come to a decision, then close this issue.

@simonbyrne
Copy link
Contributor

Another example:

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

# yet if I write this
julia> -0.0 + 0.0im
0.0 + 0.0im

@kshyatt
Copy link
Contributor

kshyatt commented Oct 5, 2022

@simonbyrne's example from 6(!!!) years ago is still valid. Is that still something we're interested in addressing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
complex Complex numbers
Projects
None yet
Development

No branches or pull requests

6 participants