Multiplication with Complex numbers and Inf give inconsistent answers - bug?

question

#1

Dear All,

I expected that this

julia> 1*(im*Inf)
0.0 + Inf*im

would give the same answer as this

julia> 1*im*Inf
NaN + Inf*im

but it didn’t! Is this a bug?

My version:

julia> versioninfo()
Julia Version 0.6.3-pre.1
Commit 41143e875d* (2018-04-18 22:58 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin17.5.0)
  CPU: Intel(R) Core(TM) i7-5557U CPU @ 3.10GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, broadwell)

#2

It is an effect of

julia> 0*Inf
NaN

Now you might say that complex numbers should actually work on the one-point compactification (such that Complex(Inf) == Complex(0,Inf)==-Complex(Inf) and Complex(0*Inf) === Complex(NaN) === Complex(Inf)+Complex(Inf)) but this breaks transitivity of Inf == Complex(Inf) == Complex(-Inf) == -Inf != Inf and would probably be quite annoying/slow to code.


#3

I’m afraid I don’t understand much of what you wrote @foobar_lv2.

Does that explain the below?

julia> Complex{Bool}(im)*Float64(Inf)
0.0 + Inf*im

julia> Complex{Float64}(im)*Float64(Inf)
NaN + Inf*im

#4

There is a special case for

julia> false*Inf
0.0

This is the case even though 0==false. Arithmetic in julia is almost never well defined modulo isequal or ==. You need to compare typeof(x)==typeof(y) && isequal(x,y) in order to get well-definedness. Excepting weird guys like BigInt and BigFloat, you can also check ===. User-defined notions of equality that allow objects of different types to be equal are common in many languages, e.g. php or javascript (ok, I have some beef with this core decision, but for better or worse 1==1.0 is gonna stay and we have to live with it).

Let’s see what the two versions you had boil down to:

1*(im*Inf) = 1* Complex(0.0,Inf) = Complex(1*0.0, 1*Inf) = Complex(0.0,Inf)

(1*im)*Inf = Complex(0,1)*Inf = Complex(0*Inf, 1*Inf)=Complex(NaN,Inf)

See also

julia> Complex(1)*(im*Inf)
NaN + Inf*im

julia> Complex(true)*(im*Inf)
0.0 + Inf*im

#5

The definition of *(::Bool, ::Number) has the following comment attached to it:

# make `false` a "strong zero": false*NaN == 0.0

Since im is a Complex{Bool}, its real part is a “strong zero”, so the product im*Inf will have a zero in the real part. However, if im is converted to any other Complex type before the multiplication with Inf, then the real part will be NaN.

julia> im*Inf
0.0 + Inf*im

julia> (UInt8(1)*im)*Inf
NaN + Inf*im

julia> (im+0)*Inf
NaN + Inf*im

julia> Complex{Float64}(im)*Inf
NaN + Inf*im

One could re-define

Base.:*(z::Complex{Bool}, x::T) where T<:Real = Complex(T(real(z)) * x, T(imag(z)) * x)

which would make im*Inf return the same result as the other three examples…


#6

My original gut feeling is that im is just the imaginary unit, by which I mean purely imaginary without even a zero real part. Just as a float has no concept of an imaginary component, or complex has no concept of quarternions etc. If it’s purely the imaginary unit there is no zero for multiplication by Inf, and hence there is no NaN.


#7

Right. But, implementing an imaginary data type has apparently not been found practically and broadly useful in general numerical computer languages.


#8

I suppose I’m just drawing the distinction between an imaginary number and a complex number. Perhaps an improved description for the im documentation might be:

The imaginary unit as a complex number with zero real part.

Maybe the c++ macro is why I think this. But I haven’t checked it’s behaviour associated with Inf and NaN.


#9

The imaginary unit is a complex number. Don’t you think mathematics and implementation align here? Also I actually like that im has a strong real zero, making it behave closer to a mathematical constant than to a measured number.


#10

Yes, I agree it could be more clear. “the imaginary unit” might mean that there is a type Imaginary. Here is the definition

const im = Complex(false, true)

#11

I’m not a trained mathematician, which might be where there is a difference in terminology.

i is the imaginary unit, which is the unit vector in the imaginary direction. Only tacitly is it an imaginary number. It’s the (non-existent?) difference between 0+1i and just i.


#12

I’m a complete layman on these matters, but your post reminded me of the RiemannComplexNumbers.jl package – I thought I’d mention it in case it helps.

edit: and maybe some of the comments on #20924 could be related as well.


#13

Great! That’ll do the trick.