# Concise way to create numeric type?

I am creating a numeric type that contains a single number field, for example:

``````struct T
v::Float64
T(v) = 0 <= v <= 1 ? new(v) : error("invalid v")
end
``````

In case you are wondering, I am doing this to ensure that all variables of type `T` have a value between 0 and 1 (or some other invariant I might want to enforce).

Now I want to define all numeric operations on `T`, simply by calling the operation on the field `v` and returning a number, like:

``````Base.:(+)(x::T, y::T) = x.p + y.p
``````

Is there a concise way of doing this for all numeric operations? I would also like to cover numeric functions. Maybe a macro?

I also tried

``````Base.convert(::Type{F}, p::T) where {F <: Number} = convert(F, p.p)
``````

in the hope that my type would get promoted automatically to a number and then the numeric operations would work, but this does not work either.

Oh I figured it out.

``````struct T <: Number
v::Float64;
T(x) = 0 ≤ x ≤ 1 ? new(x) : error("invalid v")
end

Base.convert(::Type{N}, x::T) where {N <: Number} = convert(N, x.v)
Base.promote_rule(::Type{N}, ::Type{T}) where {N <: Number} = promote_type(N, Float64)
``````

But now I am getting some strange errors. Simply executing:

``````T(0.2)
``````

results in this big error:

T(Error showing value of type T:
ERROR: MethodError: Cannot `convert` an object of type Float64 to an object of type T
This may have arisen from a call to the constructor T(…),
since type constructors fall back to convert methods.
Stacktrace:
 Pair(::Symbol, ::T) at ./pair.jl:4
 show_default(::IOContext{Base.Terminals.TTYTerminal}, ::Any) at ./show.jl:134
 display(::Base.REPL.REPLDisplay{Base.REPL.LineEditREPL}, ::MIME{Symbol(“text/plain”)}, ::T) at ./REPL.jl:122
 display(::Base.REPL.REPLDisplay{Base.REPL.LineEditREPL}, ::T) at ./REPL.jl:125
 display(::T) at ./multimedia.jl:194
 eval(::Module, ::Any) at ./boot.jl:235
 print_response(::Base.Terminals.TTYTerminal, ::Any, ::Void, ::Bool, ::Bool, ::Void) at ./REPL.jl:144
 print_response(::Base.REPL.LineEditREPL, ::Any, ::Void, ::Bool, ::Bool) at ./REPL.jl:129
 (::Base.REPL.#do_respond#16{Bool,Base.REPL.##26#36{Base.REPL.LineEditREPL,Base.REPL.REPLHistoryProvider},Base.REPL.LineEditREPL,Base.LineEdit.Prompt})(::Base.LineEdit.MIState, ::Base.AbstractIOBuffer{Array{UInt8,1}}, ::Bool) at ./REPL.jl:646

What is going on here??

And even more surprisingly, `T(0.2) + 0.2` correctly returns `0.4` without any problems.

You should define

``````Base.convert(::Type{T}, x::T) = x
``````

or convert will get into an infinite recursion when you try to convert a `T` into a `T`. (I’m guessing `show` tries to do this.)

1 Like

Here’s a nice definition of + - * / for a Galois field using a loop over those operators and macros, from one of Andreas Noack’s talks:

``````import Base: +, -, *, /

for op in (:+, :-, :*)
@eval begin
(\$op){P,T}(x::GF{P,T}, y::GF{P,T}) = GF{P,T}(\$(op)(x.data, y.data))
end
end
``````

Complete working example

``````# Scalar finite fields. P is the modulus, T is the integer type (Int16, Int32, ...)
immutable GF{P,T<:Integer} <: Number
data::T
function GF(x::Integer)
return new(mod(x, P))
end
end

# basic methods for scalar finite field
import Base: convert, inv, one, promote_rule, show, zero

function call{P}(::Type{GF{P}}, x::Integer)
if !isprime(P)
throw(ArgumentError("P must be a prime"))
end
return GF{P,typeof(x)}(mod(x, P))
end
convert{P,T}(::Type{GF{P,T}}, x::Integer) = GF{P}(x)
convert{P}(::Type{GF{P}}, x::Integer) = GF{P}(x)
convert{P,T}(::Type{GF{P,T}}, x::GF{P}) = GF{P,T}(x.data)
promote_rule{P,T1,T2<:Integer}(::Type{GF{P,T1}}, ::Type{T2}) = GF{P,promote_type(T1,T2)}
show(io::IO, x::GF) = show(io, x.data)

# define arithmetic operations
import Base: +, -, *, /

for op in (:+, :-, :*)
@eval begin
(\$op){P,T}(x::GF{P,T}, y::GF{P,T}) = GF{P,T}(\$(op)(x.data, y.data))
end
end

# Division requires slightly more care
function inv{P,T}(x::GF{P,T})
if x == zero(x)
throw(DivideError())
end
r, u, v = gcdx(x.data, P)
GF{P,T}(u)
end
(/){P}(x::GF{P}, y::GF{P}) = x*inv(y)

x, y = GF{5}(9), GF{5}(8)
@show x
@show y
@show x + y
@show x - y
@show x * y
;
x = 4
y = 3
x + y = 2
x - y = 1
x * y = 2
``````
1 Like

This is incorrect. Base already includes this definition.

@stevengj It did fix the errors… somehow it worked.

Yes, but the method that specializes on a concrete type has precedence.

``````julia> struct T <: Number
v::Float64;
T(x) = 0 ≤ x ≤ 1 ? new(x) : error("invalid v")
end

julia> Base.convert(::Type{N}, x::T) where {N <: Number} = convert(N, x.v)

julia> @which convert(T, T(0.2))
convert(::Type{N}, x::T) where N<:Number in Main at REPL:1
``````
1 Like