# Understanding how to implement numbers in Julia

To understand how to implement a number in Julia, I tried to implement my own complex number type:

``````struct MyComplex{T<:Real}  <: Number
real::T
imag::T
end

MyComplex(re::Real) = MyComplex(re,zero(re))
Base.conj(z::MyComplex) = MyComplex(z.real,-z.imag)
real(z::MyComplex) = z.real
imag(z::MyComplex) = z.imag
``````

then I define basic arithmetic

``````function Base.:+(a::MyComplex,b::MyComplex)
MyComplex(a.real + b.real, a.imag + b.imag)
end

function Base.:-(a::MyComplex,b::MyComplex)
MyComplex(a.real - b.real, a.imag - b.imag)
end

function Base.:*(a::MyComplex,b::MyComplex)
MyComplex(a.real * b.real - a.imag * b.imag, a.real * b.imag + b.real * b.imag)
end

function Base.:/(a::MyComplex,b::Real)
return MyComplex(a.real/b,a.imag/b)
end

function Base.:/(a::MyComplex,b::MyComplex)
num = a * conj(b)
den = real(b * conj(b))
return num/den
end
``````

What do I do from here so that my new MyComplex number type just works with math functions such as sin cos exp sqrt? I thought if I implement the basic arithmetic it will just work with all of the built in math functions.

Then, some of the operations fails:

``````julia> 2/MyComplex(0,2)
ERROR: promotion of types Int64 and MyComplex{Int64} failed to change any arguments
Stacktrace:
 error(::String, ::String, ::String) at ./error.jl:42
 sametype_error(::Tuple{Int64,MyComplex{Int64}}) at ./promotion.jl:306
 not_sametype(::Tuple{Int64,MyComplex{Int64}}, ::Tuple{Int64,MyComplex{Int64}}) at ./promotion.jl:300
 promote at ./promotion.jl:283 [inlined]
 /(::Int64, ::MyComplex{Int64}) at ./promotion.jl:314
 top-level scope at REPL:1
``````

as the error suggests, you need to implement the promotion rules:

because you sub-typed `Number`, as soon as you have promotion rules in place, these will start working. (because + - * / has fallback methods which perform promotions first)

5 Likes

I don’t think the interface for numbers is formally and extensively documented (yet?), but there are a couple of threads here on discourse where this topic was discussed. You might find valuable information there:

From what I see and off the top of my head, I’d say that apart from promotion rules, you’ll still be missing:

• a unary minus operator
• comparison operators
• probably a `zero` method
5 Likes

How do I implement promotion rules? I added this in my script

``````struct MyComplex{T<:Real}  <: Number
real::T
imag::T
end

MyComplex(re::Real) = MyComplex(re,zero(re))
Base.conj(z::MyComplex) = MyComplex(z.real,-z.imag)
real(z::MyComplex) = z.real
imag(z::MyComplex) = z.imag

promote_rule(::Type{MyComplex{T}}, ::Type{S}) where {T<:Real,S<:Real} = MyComplex{promote_type(T,S)}
promote_rule(::Type{MyComplex{T}}, ::Type{MyComplex{S}}) where {T<:Real,S<:Real} = MyComplex{promote_type(T,S)}
``````

following the example given for `Rational` type: https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Case-Study:-Rational-Promotions and when I try to run `promote(MyComplex(2,0),2)` it errors out

``````julia> promote(MyComplex(2,0),2)
ERROR: promotion of types MyComplex{Int64} and Int64 failed to change any arguments
Stacktrace:
 error(::String, ::String, ::String) at ./error.jl:42
 sametype_error(::Tuple{MyComplex{Int64},Int64}) at ./promotion.jl:306
 not_sametype(::Tuple{MyComplex{Int64},Int64}, ::Tuple{MyComplex{Int64},Int64}) at ./promotion.jl:300
 promote(::MyComplex{Int64}, ::Int64) at ./promotion.jl:283
 top-level scope at REPL:1
``````

EDIT: if I use `Base.promote_rule` instead of just `promote_rule` it works (ish, I need to implement some more functions), how do I know when to use `Base.` instead of the function directly?

whenever you want to add a new method for an existing function from another module, you need to either `import` it, or qualify it. So either

``````import Base.promote_rule
promote_rule(..) = ...
``````

or `Base.promote_rule(...)=...` as you did. (Same for any other module, e.g. if you want to add a method to a function from some package `PkgX`, then you’d do the same).

Often the `Base.promote_rule(...)=...` way is preferred (e.g. it’s recommended in YASGuide which is the style guide we use at my work) because it makes it more obvious that you’re extending a function from another module (since the `import` statements may be in some other file so you might forget that you’ve `import`ed the function).

If you don’t do this, you’re defining a new function instead of adding a method to an existing function.

3 Likes