Suppose I have an integer n
that will only ever take values in [0, 10]
. Should I declare it as n::Integer
to be general about it, as n::Int8
or n::UInt8
to be parsimonious or n::Int64
for a 64 bit system?
Please clarify the reasons for a newbie, e.g. style, performance.
Reference: https://docs.julialang.org/en/release-0.5/manual/integers-and-floating-point-numbers/
Updated Reference (2021): https://docs.julialang.org/en/v1/manual/types/#man-abstract-types
It's important to distinguish two different cases.
Storage: If you have a type that stores n
as one of its fields, or as a value in an array, then you should definitely consider using Int8
or UInt8
. Even if the saved space for a single value is negligible, if many instances of your type are created and stored in a collection, then the space savings can rapidly become significant. Let's say you have a Foo
type with a field n
, then you might do this:
struct Foo
n::UInt8
end
When a value is assigned to the n
field of a Foo
object, it will automatically be converted to UInt8
and an error will be raised if the value cannot be converted faithfully:
julia> Foo(123) # Ints are automatically converted to UInt8
Foo(0x7b)
julia> typeof(ans.n)
UInt8
julia> Foo(500) # if too large, an error is raised
ERROR: InexactError()
Stacktrace:
[1] Foo(::Int64) at ./REPL[1]:2
julia> Foo(-1) # ditto if too small
ERROR: InexactError()
Stacktrace:
[1] Foo(::Int64) at ./REPL[1]:2
julia> Foo(2π/π)
Foo(0x02)
If the value that's assigned is already of the correct type then no check is required so there's no overhead.
Dispatch: If you are writing a method of a function that takes n
as an argument, then there's no harm in having as loose a type annotation on the n
argument as makes sense semantically. In the case you've described, it seems that any kind of integer value would be sensible, so using n::Integer
would probably be appropriate. For example, if wanted to implement a checked constructor for Foo
objects, you could do this:
struct Foo
n::UInt8
function Foo(n::Integer)
0 <= n <= 10 || throw(ArgumentError("n not in [0, 10]: $n"))
return new(n)
end
end
Now an error is thrown if a value outside of [0, 10] is given:
julia> Foo(123)
ERROR: ArgumentError: n not in [0, 10]: 123
Stacktrace:
[1] Foo(::Int64) at ./REPL[26]:2
julia> Foo(3)
Foo(0x03)
This Foo
construction works for any kind of integer, checking that it's in the correct range, and then converting to UInt8
. This is slightly more restrictive than the built-in constructor for Foo
, which will happily take any kind of n
argument and try to convert it to UInt8
– even when the argument is not of an integer type. If that kind of behavior is desirable, you could loosen the type signature here further to n::Real
, n::Number
(or even n::Any
, although that seems excessive).
Note that there is no performance advantage to tightly typed method arguments – the specialized code for actual argument types is generated on demand anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With