Displaying complex version of numeric type


#1

I’m creating a numeric type (fixed point) and ran into the following problem when trying to print:

here’s the definition of the type

type Fixed{m, f} <: Real
    x :: Int64
end
c = complex(a,b)

println(c)


ERROR: LoadError: no promotion exists for Fixed{1,15} and Int64
Stacktrace:
 [1] promote_type(::Type{Fixed{1,15}}, ::Type{Int64}) at ./promotion.jl:161
 [2] promote at ./promotion.jl:175 [inlined]
...

i tried defining a promote_type,

function promote_type(x::Fixed{m,f}, ::Type{Int64}) where {m,f}
    x.x
end

and that gives me exactly the same error.
Then I realized, why is it trying to promote the type ?
if i do

println(a)

where a is Fixed{1,15}(0), i get the default representation,

Fixed{1,15}(0)

So it would seem that println(complex(a,b)) should simply use that representation for each of the real and imaginary parts. yes, this means that I haven’t defined a show method.

However defining a show method doesn’t seem to help.

function show(io::IO, x::Fixed{m,f}) where {m,f}
    print(io, x.x)
end

ERROR: LoadError: no promotion exists for Fixed{1,15} and Int64
Stacktrace:
 [1] promote_type(::Type{Fixed{1,15}}, ::Type{Int64}) at ./promotion.jl:161

I’ve been perusing the documentation and looking through FixedPointNumbers but can’t quite figure out what to try next.

Thanks,


#2

You defined your type as

type Fixed{m, f} <: Real
    x :: Int64
end

Therefore x must be an Int64, It seems you’re trying to assign a complex number to x.
Try the following type declaration instead.

type Fixed{m, f} <: Real
    x
end

By the way in Julia version 0.6 the keyword to use is struct instead of type.


#3

You didn’t show the part of the stack trace which indicates that the trouble is in signbit. Since your type doesn’t seem to have one, try

import Base.signbit
signbit(x::Fixed{m,f}) where m where f = false

In general, if you see something like this in a stacktrace:

 [4] signbit(::Fixed{1,15}) at ./number.jl:72
 [5] show(::IOContext{Base.Terminals.TTYTerminal}, ::Complex{Fixed{1,15}}) at ./complex.jl:154

you can proceed to

julia> less( show,(IOContext{Base.Terminals.TTYTerminal}, Complex{Fixed{1,15}}))

where we see why signbit is called.


#4

@jandehaan
yes, i should use struct, the value is not intended to be mutable.
however i don’t understand your statement about “trying to assign a complex number to x”. The assignment worked fine, i.e.

c = complex(a,b)

succeeded. it was the print of ‘c’ that failed.
@Ralph_Smith that did it. thank you. Is there anyway i could have figured that out from the documentation ? It seems like a very obscure point.

Also I could not get the ‘less’ statement you provided to work.

julia> include("test_fixed.jl")
Fixed{1,15}(0)
Fixed{1,15}(0)

julia> less(show,(IOContext{Base.Terminals.TTYTerminal}, Complex{Fixed{1,15}}))
ERROR: no method found for the specified argument types
Stacktrace:
 [1] which(::Any, ::Any) at ./reflection.jl:823
 [2] less(::Function, ::Any) at ./interactiveutil.jl:121

julia> 

I probably should have stated that I’m still using 0.6.0 …


#5

I think that’s one of the costs of implementing a low-level type. You could look at the source for the FixedPointNumbers package to see a superset of the (relevant part of the) interface for subtypes of Real. I fear that trial and error is the only way to find the minimum needed for your purposes, and the “error” part involves this sort of detective work.

Is your Fixed type in the Main scope of the REPL? Or maybe the full spec for the IO type was different in 0.6.0? Whatever signature you got in the stacktrace should work, possibly with extra module qualifications (the method must have been found if it was traced).


#6

@Ralph_Smith is correct. I misunderstood your example because the assignments of a and b were missing. In any case, look at https://github.com/JuliaLang/julia/blob/master/base/complex.jl starting on line 181. It shows (no pun intended) that the show method for complex numbers depends on functions signbit and isnan. You’ll have to implement methods for those functions that take your type as input.


#7

Great! Thanks very much for the help.


#8

I can’t help to bring back the documentation of interface for abstract types topic. Having to dive in source code as your trial and error strategy proposal suggests is not really satisfying. I do accept that my concept/interface/traits concerns (Question about abstract type interfaces) are related with OO way of thinking, but I also note that this question arises regularly.


#9

I agree that well-defined and documented interfaces, with infrastructure that enforces them, would be better than workarounds such as I described. But all this takes work, and people keep finding reasons to give higher priority to other matters.

With regard to traits etc., I’ll point to this thread on multiple traits as evidence that some of the developers (a) recognize these issues to be important and (b) understand that a good solution is likely to be difficult.


#10

and now more complications…
my definition of zero:

zero(::Type{FixedPoint{m,f}}) where {m,f} = FixedPoint{m,f}(0)

and so

zf3 = zero(FixedPoint{1,31})
println(zf3)

0.0Q1f31

works but,

zf4 = zero(Complex{FixedPoint{1,31}})
println(zf4)
ERROR: LoadError: MethodError: Cannot `convert` an object of type Int64 to an object of type Fixed.FixedPoint{1,31}
This may have arisen from a call to the constructor Fixed.FixedPoint{1,31}(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] zero(::Type{Complex{Fixed.FixedPoint{1,31}}}) at ./number.jl:131
 [2] include_from_node1(::String) at ./loading.jl:569
 [3] include(::String) at ./sysimg.jl:14
 [4] process_options(::Base.JLOptions) at ./client.jl:305
 [5] _start() at ./client.jl:371

doesn’t. That’s so confusing. I can’t find anything in complex.jl that tells me how it’s generating a zero value, but you would hope it simply calls ‘zero’ for each of the real and imaginary parts, so it should just work. I looked in FixedPointNumbers too, and cannot find a zero method, and yet it just works also.

even the error line doesn’t make sense. Int64 is in the very definition of FixedPoint, why should a convert function to go from Int64 to FixedPoint even be required ??

FixedPoint{1,31}(0) is the very definition of a fixed point value using the constructor.


#11

convert from Integer is definitely part of the Real interface; it can substitute for a simple constructor (but not vice-versa, as you were hoping here), and it’s used for several things like this. Also see the manual section on constructors:

If you want to define a constructor for a lossless conversion from one type to another, you should probably define a convert method instead.


#12

The @which macro may be helpful here. It tells you which specific method will be invoked.

julia> @which zero(Complex{FixedPoint{1,31}})
zero{T<:Number}(::Type{T}) at number.jl:157

and number.jl:157 reads

zero(::Type{T}) where {T<:Number} = convert(T,0)

Therefore you’ll need something like

convert(::Type{FixedPoint{m,f}}, x::Int) where {m,f} = FixedPoint{m,f}(x)

By the way, you’ll find that when you define your own Number type, you’ll have to define many methods to match. Consider using Julia package AbstractNumbers.


#13

really, thanks very much to everyone for the help.

one other thing i should mention since it’s a bit of trap.

if you define your convert function, you can’t forget to do

‘’’
import Base: convert
‘’’

or you’ll get the exact same message. it took me an embarrasingly long time to figure that out …


#14

Thank you very much for this answer and the link.
As a Julia newbie, as was afraid that my questions about interface were totally irrelevant :grinning: