Alternative Complex type

Is there a complex type saving the angle and the radius instead of the imaginary and the real part?

I think if there are a lot of multiplications this would be faster.

I don’t think there is but it would be relatively easy to implement, e.g.

struct PolarComplex{T<:Real} <: Number
    R::T
    θ::T
    PolarComplex(R::T, θ::T) where T <: Real = 
        new{T}(R, mod(θ,2π))
end

PolarComplex(x::Real, y::Real) = PolarComplex(promote(x,y)...)
 
Base.angle(x::PolarComplex) = x.θ
Base.abs(x::PolarComplex) = x.R

Base.:*(x::PolarComplex, y::PolarComplex) = 
    PolarComplex( abs(x)*abs(y), angle(x)+angle(y) )

z1  = (exp(2im)) * (3*exp(4im))
z1 = PolarComplex(abs(z1), angle(z1))

z2 = PolarComplex(1.0,2.0) * PolarComplex(3.0,4.0) 

@assert z1 == z2

The promotion, conversion and constructor part is a bit more complicated but you could pretty much copy the Complex implementation in Base.

I don’t know if it’s any faster though (I shouldn’t do the mod probably).

5 Likes

I think there are still some safety checks going on, but I have no idea how to remove those.

> z1p = PolarComplex(abs(z1), angle(z1))
> @code_typed z1p*z1p
CodeInfo(
1 ─ %1  = (Base.getfield)(x, :R)::Float64
│   %2  = (Base.getfield)(y, :R)::Float64
│   %3  = (Base.mul_float)(%1, %2)::Float64
│   %4  = (Base.getfield)(x, :θ)::Float64
│   %5  = (Base.getfield)(y, :θ)::Float64
│   %6  = (Base.add_float)(%4, %5)::Float64
│   %7  = (Base.rem_float)(%6, 6.28319)::Float64
│   %8  = (Base.eq_float)(%7, 0.0)::Bool
│   %9  = (Base.and_int)(%8, true)::Bool
│   %10 = (Base.and_int)(%9, true)::Bool
└──       goto #3 if not %10
2 ─ %12 = (Base.copysign_float)(%7, 6.28319)::Float64
└──       goto #6
3 ─ %14 = (Base.lt_float)(0.0, %7)::Bool
│   %15 = (Base.eq_float)(0.0, %7)::Bool
│   %16 = (Base.and_int)(%15, false)::Bool
│   %17 = (Base.or_int)(%14, %16)::Bool
│   %18 = (%17 === true)::Bool
│   %19 = (Base.not_int)(%18)::Bool
└──       goto #5 if not %19
4 ─ %21 = (Base.add_float)(%7, 6.28319)::Float64
└──       goto #6
5 ─       goto #6
6 ┄ %24 = φ (#2 => %12, #4 => %21, #5 => %7)::Float64
│   %25 = %new(PolarComplex{Float64}, %3, %24)::PolarComplex{Float64}
└──       goto #7
7 ─       return %25
) => PolarComplex{Float64}

This is only because of the mod(θ,2π) call (it would probably be more exact to use mod2pi(θ) here instead). You could leave this out instead, but if you’re doing a lot of operations, it could accumulate quite quickly and result in a loss of precision.

3 Likes

You could remove it yes, I mainly put it there because otherwise my assertion at the end was failing.

You can always overload the == operator to do that, but if you’re calling this method quite often, it’s probably better to use mod2pi in the constructor.

Something that might make this implementation better is to make the Pi part of the radius implicit. This would make it better able to represent angles that are multiples of Pi.