Read their own future sections for containers:
The Control Flow chapter has already covered Booleans.
julia> pkgchk.( [ "julia" => v"1.0.3", "Missings" => v"0.3.1", "Formatting" => v"0.3.4", "CategoricalArrays" => v"0.4.0" ] );
julia> typeof( 2 ) Int64 julia> typeof( 3.0 ) Float64
Use 'const' (the constant modifier) generously, especially for global variables that you want to (ab-)use in function. This improves both bug-detection and execution speed, often dramatically. Or avoid globals altogether.
IMHO, primitive native-CPU type objects are best. This improves bug-detection, memory footprint, and speed.
Use broader types only when there is a clear need (such as in function arguments, in which you really want the function to have broader and more generic reusable functionality). This can be the case, e.g., if you want to feed the function to an automatic differentiator (see Andreas' note below.)
More generic numeric types—such as Real
(which can be either Int*
or Float*
); and Number
(which can be either Real
or Complex
)—are almost always useless overkill in specific scientific application programming. The Any
type is even broader, so avoid it like the undead dead. (The exception to the 'do not use Any' rule is printing, where Any
printing can indeed be quite useful.)
Unfortunately, global variables cannot have types in Julia 1.0 yet.
My advice here is controversial. Andreas Noack, for example, points out that stricter signatures might be more informative about the function but they won't make the function faster per se. The method will become specialized for the specific input types regardless of signature. An important counter example to your [preference for broader types] in scientific computations is automatic differentiation. This is a very powerful computational method. It should be used much more widely than it currently is. The main requirement is that one can pass special number types through and restricting to e.g. Float64
is therefore typically not a good idea. Annotating the variables also will not give any speed up. It might reveal a type instability in a function but we usually recommend not to annotate variables with types.
This is a common beginner's mistake.
statement | result | explanation |
---|---|---|
v= Float64 | a type | desired? v is now an alias for the Float64 type. x=v(NaN) now declares a variable. |
v= Float64(NaN) | an initialized variable | v is a now a variable initialized with NaN. |
Inquiring explained how you can learn more about both types and variables (use typeof
and dump()
).
Variables (object instances) in Julia are always typed. (A type can be generic, like a Number
[which could be an nteger or a float] or an Any
[which can indeed hold anything]). However, Julia makes it easier on the user by often infering a “default” types. x= 12
will make x
of type Int64
, x= 12.0
a Float64
, x= 'c'
a Char
, x= "s"
a String
, and so on. The two most commonly used (numeric) types in julia are the defaults
Int
(usually identical to Int64
).
Float64
(there is no generic Float
).julia> typeof(1), typeof(1.0) (Int64, Float64)
There are bigger and smaller types, as well as unsigned integer types. The exact number hierarchy is as follows:
[download only julia statements] Number Complex Real AbstractFloat: BigFloat Float64 Float32 Float16 Integer BigInt Bool Signed: Int128 Int64 Int32 Int16 Int8 Unsigned: UInt128 Int64 UInt32 UInt16 UInt8 Irrational Rational
Float
type, and the Int
type aliases for Int64
on all modern 64-bit CPUs. See also Inquiring for subtype()
and supertype()
functions. However…
Real
. Function arguments that work only with Floats use AbstractFloat
. Function arguments that work only with Ints use Integer
.
missing
variable. The Missing type with missing value (note the capitalization) will be covered in detail in chapter Missings. When missing values are involved, dealing with types becomes messier.Most users will be on later-model Intel CPUs. On a Xeon i7-7700, I ran a simple test program:
[download only julia statements] using BenchmarkTools M= 1_000_000 T= Int16; println(T); @btime begin v= Vector{T}(rand(5:999999, M)); v= v+v.*v-mod.(v, v-3); end;
The following table is suggestive about relative speeds of using different types:
Type | Int8 | Int16 | Int32 | Int64 | Int128 | Float16 | Float32 | Float64 | BigFloat |
---|---|---|---|---|---|---|---|---|---|
Time | 1.0 | 1.0 | 1.0 | 1.2 | 1.9 | 2.7 | 0.9 | 1.0 | 120.0 |
Please see the chapter on Missings. The Missings chapter also discusses NaN, which are not missings, but are part of the ordinary IEEE Float definitions. (Roughly speaking, using missing raises calculation time by a factor of 3. Using NaN has no speed penalty.)
Already discussed in Inquiring, typeof()
is the key function. The content eltype
(“element-type”) works for both scalars and for the contents of arrays:
julia> typeof( 2 ) ## scalar test Int64 julia> typeof( [ 2, 3 ] ) ## type of whole vector Array{Int64,1} julia> eltype( 2.0 ) ## element type for a scalar Float64 julia> eltype( [ 2, 3 ] ) ## element types for arrays Int64
julia> eltype( 1 ) <: Number true julia> eltype( 2 ) <: Real true julia> eltype( 1.0 ) <: Int false
Warning: This does not work when the type is a union that includes support for missings.
julia> eltype( [ 1, missing ] ) Union{Missing, Int64} julia> eltype( [ 1, missing ] ) <: Int64 false
Instead, you can test for a Union-with-missing type or simply eliminate the missing first:
julia> eltype( [ 1, missing ] ) <: Union{Missing,Int64} ## standard test true julia> eltype( collect(skipmissing( [ 1, missing, 3 ] )) ) <: Int64 ## or remove missings first true
Use ==
for value comparisons. Use ===
for value plus type plus memory-location comparisons:
julia> Int8(2) == Int32(2) ## compare value only true julia> x= 3; y= 3; (x === y) ## quasi-dereferenced variables' contents are type and value identical true julia> Int8(2) === Int32(2) ## but type here differs false julia> x= [ 2 ]; y= [ 2 ]; (x === y) ## and pointed-to storage locations are different false
julia> convert(Float32, 1) ## Int to Float32 1.0f0 julia> Char(round(Int, 12.4)) ## Float64 (12.4) to Int (12) to Char (\f) '\f': ASCII/Unicode U+000c (category Cc: Other, control) julia> oftype( 12.0, 1 ) ## convert arg2 (1) to type of arg1 (12.0), i.e., Int to Float64 1.0 julia> promote(1, 2.5, true) ## promotion = make them all the same (lcd) type (1.0, 2.5, 1.0)
julia> convert( Vector{Float16}, [ 1, 2 ] ) ## Vector{Int} to Vector{Float16} 2-element Array{Float16,1}: 1.0 2.0 julia> String( [ 'a', 'b' ] ) ## Preview: smart String converter of Char. see Strings chapter. "ab" julia> string( [ 'a', 'b' ] ) ## lowercase = just stringify "['a', 'b']"
1_000_000.123_456
.
parse(Float64, "12.2")
, not Float64("12.2")
.julia> typemax(Float64) ## largest inf Inf julia> floatmax(Float64) ## largest non-inf float...richer than Jeff Bezos 1.7976931348623157e308 julia> maxintfloat(Float64) ## largest int representable in Float64 without loss 9.007199254740992e15 julia> typemin(Int16) ## most negative –32768
The smallest (tiniest) value above zero that is representable,
eps(T)
is eps(one(T))
i.e. the distance bewtween floats at one aka machine epsilon. The smallest value above zero is eps(zero(T))
which for Float64
is 5.0e-324
julia> eps(Float64) 2.220446049250313e–16
nextfloat
gives the next representable float. RoundNearest
can inform IEEE-754 operation.julia> isfinite.((NaN, Inf, -Inf, 0.0)) (false, false, false, true) julia> isnan.((NaN, Inf, -Inf, 0.0)) (true, false, false, false) julia> isinf.((NaN, Inf, -Inf, 0.0)) (false, true, true, false)
See also the chapter on Missings and NaN.
To round a Float64
to Int
:
julia> convert(Int, 15.0) ## 15.0 can be cleanly converted 15 julia> convert(Int, 15.3) ## 15.3 cannot be cleanly converted ERROR: InexactError: Int64(Int64, 15.3) Stacktrace:
round | to nearest integer |
---|---|
floor | towards -Inf |
ceil | towards +Inf |
trunc | towards 0 |
julia> round(Int, 15.3) ## you must decide on your intent 15 julia> typeof( ans ) ## Julia was smart enough to change the type Int64 julia> convert( Vector{Int}, [ trunc(12.2), floor(12.2), trunc(–12.3), floor(–12.3) ]) 4-element Array{Int64,1}: 12 12 –12 –13
julia> modf(12.312) ## fraction comes first, then integer part (but still a Float) (0.3119999999999994, 12.0)
julia> round(123.4567; digits=2) ## same operation as Float->Int 123.46
julia> round.( [ 123.456789, 1.23456, 0.0123456, 0.000123456 ]; sigdigits=2 ) 4-element Array{Float64,1}: 120.0 1.2 0.012 0.00012
julia> mround( v::AbstractFloat, nearest::AbstractFloat )= round( v/nearest )*nearest; julia> mround( 24.4324245, 0.05 ) ## uh oh. we need to do better 24.450000000000003 julia> mround( v::AbstractFloat, nearest::AbstractFloat )= round( round( v/nearest )*nearest; digits= Int(1+abs(floor(log10(nearest)))) ); julia> mround( 24.432, 0.05 ) ## ugly function, but does the job 24.45
The usual caveats on floating-point precision apply:
julia> (sqrt(5.0))^2 == 5.0 false
because
julia> sqrt(5)^2 5.000000000000001
If missing or NaN are a concern, isequal functions slighly differently.
julia> isequal([1., NaN], [1., NaN]) true julia> [1., NaN] == [1., NaN] false
==
, isequal does not consider -0.0 and +0.0 to be the same.
Instead, use isapprox
, which is in UTF the ≈
operator:
julia> isapprox( (sqrt(5))^2 , 5.0 ) true julia> isapprox( (sqrt(5))^2 , 5.0, atol=0.0001 ) true
julia> using Formatting julia> sprintf1("%'d", 1_000_000) "1,000,000" julia> sprintf1("%'f", 1_000_000.0) "1,000,000.000000" julia> format(100_000.0, width=10, signed=true, leftjustified=true) ## many more options "+100000 "
sprintf1.("%.3f", 1:3)
and sprintf1.("%.3f", [ 1 2 ; 3 4])
C-style printf and sprintf work as macros, so they require '@' prefixes:
julia> using Printf julia> @printf("%12.5f", pi) 3.14159
sprintf1.("%.3f", 1:3)
and sprintf1.("%.3f", [ 1 2 ; 3 4])
Most standard mathematical functions (e.g., exp, sin, etc.) work as expected. Some less common but very useful functions
Function | Explanation |
---|---|
abs2(8) | the square (64) |
cbrt(8) | the cuberoot (2.0), type Float! |
expm1(0) | =exp(0)-1 , here 0 |
log1p(0) | =log(1+0) , here 0 |
log10(100) | log 10, here 2.0 |
sinpi(0.5) | =sin(pi*0.5) , here 1.0, better accuracy |
cospi(0.25) | =cos(pi*0.25) , here 0.707… |
sind(270) | = -1.0 input is in radians |
Many other useful functions are now defined in SpecialFunctions.jl. For example,
Function | Explanation |
---|---|
erf(x) | error function |
gamma(x) | gamma function |
lgamma(x) | log gamma function |
lfact(x) | log factorial (log x!) |
beta(x,y) | beta function |
lbeta(x,y) | log beta function |
There are '=' operators that do '' and then assign the result, such as x+=1
which adds 1 to x.
1_000_000
.
2^64
gives zero without warning.
parse("12")
, not Int("12")
julia> 10/7 ## note automatic promotion to float in division 1.4285714285714286 julia> trunc(10/7) ## trunc of int division, still results in float 1.0 julia> div(10, 7) ## true integer division with integer result 1 julia> rem(10, 7) ## modulus of division 3 julia> 10%7 ## same modulus 3 julia> divrem(10,7) ## int division and modulus (1, 3) julia> 10//7 + 1 ## // maintains fractions 17//7
julia> (0/0, 1.0/0.0, 1/0, 1//0) ## converts to Float with IEEE special values (NaN, Inf, Inf, 1//0) julia> mod(1,0) ## uh oh: no IEEE ERROR: DivideError: integer division error Stacktrace: julia> 1%0 ## uh oh: no IEEE ERROR: DivideError: integer division error Stacktrace:
You may or may not want to trap these exceptions.
julia> string(29; base=5) "104" julia> println(digits(29, base=5)) [4, 0, 1] julia> println( reverse(digits( 29, base=5, pad=6 )) ) ## padding to 6 digits [0, 0, 0, 1, 0, 4] julia> string(253; base=2) "11111101"
Julia has faster oct()
and hex()
functions built in.
julia> const x= 3; 2x 6 julia> 2^2x ## warning: not 4*3=12, but 2^6. 64
An iterator is like a range of integers, typically used to index arrays, dictionaries, or other objects. Thus, they are mostly discussed in the Arrays Vector chapter.
julia> for i=0:(2^3–1); println(bitstring(i)[(end–2):end]); end#for ## 3 items, able to take 2 values (0 or 1) 000 001 010 011 100 101 110 111 julia> for i=0:7; println(digits( i; base=2, pad=0 )); end#for ## here, trailing zeros are removed Int64[] [1] [0, 1] [1, 1] [0, 0, 1] [1, 0, 1] [0, 1, 1] [1, 1, 1]
julia> function permutations(v::Vector{T}) where T <: Any nbase= length(v) results= Vector{Vector{T}}(undef,0) for it=0:(nbase^nbase–1) push!( results, v[digits( it; base=nbase, pad=nbase ) .+ 1] ) end#for results end#function## permutations (generic function with 1 method) julia> permutations( [ 'a', 'b', 'c' ] ) ## or try ["apple", "strawberry", "orange" ] 27-element Array{Array{Char,1},1}: ['a', 'a', 'a'] ['b', 'a', 'a'] ['c', 'a', 'a'] ['a', 'b', 'a'] ['b', 'b', 'a'] ['c', 'b', 'a'] ['a', 'c', 'a'] ['b', 'c', 'a'] ['c', 'c', 'a'] ['a', 'a', 'b'] ⋮ ['a', 'a', 'c'] ['b', 'a', 'c'] ['c', 'a', 'c'] ['a', 'b', 'c'] ['b', 'b', 'c'] ['c', 'b', 'c'] ['a', 'c', 'c'] ['b', 'c', 'c'] ['c', 'c', 'c']
julia> @enum FRUIT apple=1 orange=2 kiwi=3 julia> f(x::FRUIT)= "I'm a FRUIT with value: $(Int(x))" f (generic function with 1 method) julia> f(apple) "I'm a FRUIT with value: 1" julia> instances(FRUIT) (apple::FRUIT = 1, orange::FRUIT = 2, kiwi::FRUIT = 3)
To convert integer vectors into categoricals vectors:
julia> using CategoricalArrays julia> const y= categorical( rand('a':'d', 1000) ); ## 1,000 random characters, from 'a' to 'd' julia> levels(y) ## the principally useful operation for Categories 4-element Array{Char,1}: 'a' 'b' 'c' 'd'
A number of features were not discussed.
//
).
~
, &
, |
, <<
, >>
, >>>
, xor
.
x= Vector{Float64}(undef,3); y= x[1]+2
.)
x=Union{Int,String}
, which would allow both x=1
and x="hi"
, but not x=1.0
. Such unions are especially important for dealing with missing observations. It has also not yet discussedAny
, which is an extremely polygamous wildcard form of Union.