Lossless type promotion

What is the correct, generic way to figure out if values of a type T can be converted into another type S without loss of precision?

Base.typejoin cannot be used for this (eg for (Int8, Int16), it returns Signed). promote_type would allow Int64 to Float64, which is not always lossless.

I want something like

mypromote(Int8, Int64) == Int64
mypromote(Int32, Float64) == Float64
mypromote(Int64, Float64) == Real

but I wonder if I can implement it without a lot of special cases.

To me it seems you could write something that would cover all of the “machine numbers” using sizeof, testing whether a type is signed, and testing whether it’s AbstractFloat or Integer. You’d also have to implement a lookup involving these three values to return an appropriate type, and hence may need to mark it as Base.@pure. (Though with constant-propagation, who knows?)

Of course this won’t cover the whole zoo of number types that Julia now supports (complex numbers, dual numbers, physical units, intervals, etc.). Being generic on that scale is a much harder problem.

1 Like

What he said.

import Base: promote_rule

for I in (:Int8, :Int16, :Int32, :Int64, :Int128)
   for F in (:Float16, :Float32, :Float64)
       @eval begin
           if sizeof($I) < sizeof($F)
               promote_rule(::Type{$I}, ::Type{$F}) = $F
           elseif sizeof($I) < sizeof(widen($F))
               promote_rule(::Type{$I}, ::Type{$F}) = widen($F)
           elseif sizeof($I) < sizeof(widen(widen($F)))
               promote_rule(::Type{$I}, ::Type{$F}) = widen(widen($F))
           else
               promote_rule(::Type{$I}, ::Type{$F}) = BigFloat
           end
       end  
   end
end