# Norm of Number type I define

I have implemented my own `Type` of `Real` (let’s call it `My`)

Julia’s boast holds true: having defined “all” the operations for `My`, Julia is “unreasonably” effective at giving me all sorts of matrix algebra operations at zero additional work on my side.

But then I want to compute the `LinearAlgebra.norm`, and things go sour, although I have implemented all the maths I expect to be needed for an Euclidean norm.

What I do not understand is what do I need to provide to get unreasonnable success.

My attempt below is problematic because

1. I don’t get a norm
2. Error messages have pushed me into implementing conversion to `Float64`. In the real world (as opposed to this MnonWE) this conversion would be lossy, and my norm would be wrong
3. Error messages have pushed me to implement `LinearAlgebra.norm`. Here I have lost the"unreasonnable effectivity" of not having to think what packages `My` is going to compile together with.

Of course I could write my own `norm` function, but then I loose any `LinearAlgebra` functionality that needs a norm…

So here are my attempts.

``````struct My <: Real
x::Float64
end
Base.:(+)(a::My,b::My)   = My(a.x+b.x)
Base.:(-)(a::My,b::My)   = My(a.x-b.x)
Base.:(*)(a::My,b::My)   = My(a.x*b.x)
Base.:(/)(a::My,b::My)   = My(a.x/b.x)
Base.:(^)(a::My,b::My)   = My(a.x^b.x)
Base.:(^)(a::My,b::Real) = My(a.x^b)
Base.abs(a::My)          = My(abs(a.x))
Base.sqrt(a::My)         = My(sqrt(a.x))
Base.isnan(a::My)        = isnan(a.x)
Base.:(<)(a::My,b::My)   = a.x<b.x
Base.AbstractFloat(a::My) = a.x
Base.convert(::Type{Float64},a::My) = a.x

using LinearAlgebra
LinearAlgebra.norm(a::My) = abs(a)
v = My.([1,2,3])
@show norm(v)
``````

You need to define promotion rules. Right now, `+(::My, ::My)` works, but `+(::My, ::AnotherNumberType)` and `+(::AnotherNumberType, ::My)` do not. In your example, `norm` fails because it calls `+(x::Float64, y::My)`, which has a fallback that promotes its arguments to a common type. To handle this, you need to define what common type that should be. This is done by adding `promote_rule` methods. One example could be

``````Base.promote_rule(::Type{My}, ::Type{T}) where {T<:Number} = promote_type(Float64, T)
``````

That way, the type `My` behaves like a `Float64` when promoted.

You don’t need to implement conversion to `Float64`, you only need to implement promotion with `Float64`. e.g.

``````Base.promote_rule(::Type{My}, ::Type{Float64}) = My
``````

(The reason for this is that `norm` needs to promote any type to a floating-point type for accumulation, and for accuracy/overflow reasons asks you to promote to at least double precision.)

You’ll also want:

``````Base.float(x::My) = x
``````

(`My` is already a floating-point type … more generally, if you had `My{T}` then you want to convert to `My{float(T)})`.

2 Likes

Yes, I have a MWE now, thanks to your advice. The norm is of type `My`, so that resolves my concern about a lossy conversion. I had to implement `Base.isnan`, `Base.inf` and as you said `Base.float`, and no need for overloading `LinearAlgebra.norm` (good!!!)

I had to make another change though:

``````struct My <: Number
``````

``````struct My <: Real
``````

Now I wonder how my real code is going to like that! - let’s find out! … YES

In practice you’d probably want a `My` type out of promotion. e.g. suppose `My` is a quaternion type.

More generally, you probably want something like `My` to be a parameterized type:

``````struct My{T<:Real} <: Real
x::T
end

Base.:(+)(a::My,b::My)   = My(a.x+b.x)
Base.:(-)(a::My,b::My)   = My(a.x-b.x)
Base.:(*)(a::My,b::My)   = My(a.x*b.x)
Base.:(/)(a::My,b::My)   = My(a.x/b.x)
Base.:(^)(a::My,b::My)   = My(a.x^b.x)
Base.:(^)(a::My,b::Real) = My(a.x^b)
Base.abs(a::My)          = My(abs(a.x))
Base.sqrt(a::My)         = My(sqrt(a.x))
Base.isnan(a::My)        = isnan(a.x)
Base.isinf(a::My)        = isinf(a.x)
Base.isfinite(a::My)        = isfinite(a.x)
Base.:(<)(a::My,b::My)   = a.x<b.x

Base.promote_rule(::Type{My{T}}, ::Type{S}) where {T,S<:Real} = My{promote_type(Float64, T)}
Base.float(x::My) = My(float(x.x))
Base.convert(::Type{My{T}}, x::Real) where {T} = My(convert(T, x))
Base.convert(::Type{My{T}}, x::My) where {T} = My(convert(T, x.x))
``````

and then you get

``````julia> using LinearAlgebra

julia> norm(My.([1,2,3]))
My{Float64}(3.7416573867739413)
``````
1 Like

Note also that it’s easier to write this sort of code as:

``````for f in (:abs, :sqrt)
@eval Base.\$f(a::My) = My(\$f(a))
end
for f in (:isnan, :isinf, :isfinite)
@eval Base.\$f(a::My) = \$f(a)
end
``````
1 Like

Yes!

My real code now works, thanks to you, and it has all the features you describe: parametric type, eval and convert.

But interestingly, you guys seem to get it to work with `My <: Real` while I had to move to `My<:Number` (well, if it works…)

To get everything to work, inclusive `LinearAlgebra.vecnormalise` I ended up overloading `Base.isinf, isginite, typemax, typemin, floatmax, floatmin, eps` and `float`.