I would have expected real(Number) -> Real
, but instead I got real(Number) -> Int
. One can argue whether real(Number) -> Real
is truly the right definition, but clearly real(Number) -> Int
makes no sense.
The problem is caused by the following three lines:
Can you provide a code snippet of exactly what you are tried, and what you got?
julia> real(Number)
Int64
fredrikekre provided the obvious answer, but I assume you were asking in what context I discovered this issue. I am currently working on a LogNumber
type that stores a floating point number as sign times logarithm of absolute value to avoid over-/underflow, i.e.
type LogNumber{S,L} <: Number
sign::S
logabs::L
end
Base.convert(::Type{<:LogNumber}, x::LogNumber) = x
Base.convert(::Type{T}, x::LogNumber) where {T <: Number} = convert(T,x.sign) * exp(convert(real(T), x.logabs))
I then called broadcast(f,v)
with arguments such that it should have returned Vector{LogNumber}
, but somehow broadcast
allocated a Vector{Number}
instead. Calling setindex!
on this vector called convert(::Type{Number}, ::LogNumber}
which in turn called convert(real(Number), x.logabs) == convert(Int, x.logabs)
which resulted in an InexactError()
.
Unfortunately, I did not manage to construct a MWE for the broadcast
call, and in any case there are things which are at least dubious in the above code, but the point remains that real(Number) -> Int
seems wrong, and debugging would have been easier for me if either real(Number) -> Real
or real(Number) -> Error()
.
I agree that either returning Real
or throwing seem better than the current definition. In general, calling real
on an abstract type is suspicious. The docstring says: "Return the type that represents the real part of a value of type T". For an abstract type, there is no way of knowing. After all, one could define a new subtype of T
any moment. In this particular case, Real
might be a reasonable upper bound, but surely, there are valid exceptions. It's a bit like the dreaded promote_op
. (BTW, Base.promote_op(real, Number)
gives Number
.)
That said, your convert
definition likely should convert(T, ...)
at the end to be sure to return a T
, thereby circumventing the problem in this particular case. And, of course, convert(Number, ::LogNumber)
should be a no-op.
It would be nice to remove zero(Number) === 0
(and one(Number)
, and similar for other abstract types). That's what's causing this. I wonder how disruptive that would be.
@JeffBezanson, the difficulty is that this may cause some code to fail if inference fails, rather than just to be slow. e.g. will sum(Number[])
fail if you remove the zero
method?
Most helpful comment
I agree that either returning
Real
or throwing seem better than the current definition. In general, callingreal
on an abstract type is suspicious. The docstring says: "Return the type that represents the real part of a value of type T". For an abstract type, there is no way of knowing. After all, one could define a new subtype ofT
any moment. In this particular case,Real
might be a reasonable upper bound, but surely, there are valid exceptions. It's a bit like the dreadedpromote_op
. (BTW,Base.promote_op(real, Number)
givesNumber
.)That said, your
convert
definition likely shouldconvert(T, ...)
at the end to be sure to return aT
, thereby circumventing the problem in this particular case. And, of course,convert(Number, ::LogNumber)
should be a no-op.