promote_type(Int16, Float16)
should produce Float32
and promote_type(Int32, Float32)
should produce Float64
, the smallest available types on my system which represent all of the integer values without loss. However, these two methods produce Float16
and Float32
respectively, which cannot accurately represent all of the necessary values.
This is a bug right?
_ _ _(_)_ | A fresh approach to technical computing
(_) | (_) (_) | Documentation: https://docs.julialang.org
_ _ _| |_ __ _ | Type "?help" for help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 0.6.1 (2017-10-24 22:15 UTC)
_/ |\__'_|_|_|\__'_| | Official http://julialang.org/ release
|__/ | x86_64-apple-darwin14.5.0
help?> promote_type
search: promote_type
promote_type(type1, type2)
Determine a type big enough to hold values of each argument type without loss, whenever possible. In some cases, where no type exists to which both types can be promoted losslessly, some loss is tolerated; for example,
promote_type(Int64, Float64) returns Float64 even though strictly, not all Int64 values can be represented exactly as Float64 values.
julia> promote_type(Int64, Float64)
Float64
julia> promote_type(Int32, Int64)
Int64
julia> promote_type(Float32, BigInt)
BigFloat
julia> promote_type(Int32, Float32)
Float32
help?> maxintfloat
search: maxintfloat
maxintfloat(T)
The largest integer losslessly representable by the given floating-point DataType T.
maxintfloat(T, S)
The largest integer losslessly representable by the given floating-point DataType T that also does not exceed the maximum integer representable by the integer DataType S.
julia> maxintfloat(Float32)
1.6777216f7
help?> typemax
search: typemax TypeMapLevel TypeMapEntry typemin TypeName
typemax(T)
The highest value representable by the given (real) numeric DataType.
julia> typemax(Int32)
2147483647
julia> Int32(Float32(16777217))
16777216
julia> maxintfloat(Float16)
Float16(2048.0)
julia> typemax(Int16)
32767
julia> Int16(Float16(2049))
2048
It was an intentional choice. By the same logic should Int64
and Float64
promote to Float128
?
The spec for promote_type
does include a clause for this, saying that the method should "Determine a type big enough to hold values of each argument type without loss, whenever possible."
So it seems that promote_type(Int64, Float64)
should return Float128
only if my system has it. My system does not have Float128
.
The Int128
type is hypothetical, but hopefully you see my point – once hardware catches up, we will have a Int128
type. The current behavior feels fairly coherent with other promotions we do these days. We don't, e.g. promote Int8
+ UInt16
to Int32
even though Int32
can hold all the possible argument values; instead we promote to the larger of the two type – UInt16
. So this may just be a matter of fixing the documentation.
If you feel the current behavior is correct, then the documentation needs to change. However, the "without loss, whenever possible." property is pretty nice.
On my computer, promote_type(Int8, UInt16)
returns Int64
which can indeed hold all possible values of either of its arguments.
I can think of two easy ways to fix this problem.
Change the behavior of promote_type
to return one wider floating point types in the two previously mentioned cases.
Change the documentation to say something like "without loss, in most cases." and then include the example
julia> promote_type(Int32, Float32)
Float32
promote_type(Int8, UInt16)
is now UInt16
: https://github.com/JuliaLang/julia/issues/9292. There was a lot of discussion of this issue and we're pretty happy with the simplicity and intuitiveness of the new rule. It would potentially make sense to have widen
methods (or some other function) that can give a common type that's large enough to hold all values of two argument types.
Also Float32 * Float32
can produce values that a Float32
cannot hold and yet is not promoted to Float64
for good reasons. The promotion behaviour of promote_type(Int32, Float32)
is almost secondary to this.
In general, we've found that widening of types is a thing that should be explicitly asked for when retention of precision is required. This is not what we originally believed going into this, which was much closer to one should retain precision when possible up to hardware limitations.
@mschauer The documentation of promote_type
does not make any guarantees about the output being able to hold the result of binary expressions on the types given as input. promote__type
makes the guarantee that it will return a type which can losslessly represent both of the types in it's input. promote_type(Float32, Float32)
does not promise to produce a type which can hold any Float32 * Float32
.
Again, all that I wish to point out in this issue is a discrepancy between what the documentation of promote_type
promises and what promote_type
actually does.
Most users, after reading the help entry for promote_type
, would probably be pretty bamboozled when they see promote_type(Int8, UInt16) = UInt16
or promote_type(Int16, Float16) = Float32
or even (new one) promote_type(Int64, Float16) = Float16
.
It looks like it is time to change the documentation for promote_type
. I think saying something along the lines of "promote_type represents the default promotion behavior in Julia when mathematical operators are given arguments of differing types, and generally tries to return a type which can at least approximate most values of either type without widening the type." would be better, as long as the really counter intuitive cases are mentioned as examples to avoid bugs.
A smallest_containing_type
(or widen
, if you prefer) function would be very useful to preserve the old behavior of promote_type
. I think several people, myself included, must have read the documentation of promote_type
and assumed it was such a function.
Exactly, I am just arguing that promote_type
does what "promote_type actually does" because one of its main uses is to be called by functions to obtain the right container type for an operation with mixed operands. So yes to everything.
Most helpful comment
Again, all that I wish to point out in this issue is a discrepancy between what the documentation of
promote_type
promises and whatpromote_type
actually does.Most users, after reading the help entry for
promote_type
, would probably be pretty bamboozled when they seepromote_type(Int8, UInt16) = UInt16
orpromote_type(Int16, Float16) = Float32
or even (new one)promote_type(Int64, Float16) = Float16
.It looks like it is time to change the documentation for
promote_type
. I think saying something along the lines of "promote_type represents the default promotion behavior in Julia when mathematical operators are given arguments of differing types, and generally tries to return a type which can at least approximate most values of either type without widening the type." would be better, as long as the really counter intuitive cases are mentioned as examples to avoid bugs.A
smallest_containing_type
(orwiden
, if you prefer) function would be very useful to preserve the old behavior ofpromote_type
. I think several people, myself included, must have read the documentation ofpromote_type
and assumed it was such a function.