User Tools

Site Tools


numbers

Read their own future sections for containers:

The Control Flow chapter has already covered Booleans.

snippet.juliarepl
julia> pkgchk( [  "julia" => v"1.0.2", "Missings" => v"0.3.1", "Formatting" => v"0.3.4", "CategoricalArrays" => v"0.4.0" ] )

Numeric Scalars and Types

snippet.juliarepl
julia> typeof( 2 )
Int64

julia> typeof( 3.0 )
Float64

Important General Advice

USE CONST LIBERALLY

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.

DECLARE YOUR FUNCTION SIGNATURES (VARIABLES) AS NARROWLY TYPED AS POSSIBLE

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.

DO NOT CONFUSE DECLARATIONS OF TYPES AND OBJECTS

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()).

Numeric Types and Type Conversions

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).
snippet.juliarepl
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:

snippet.text
[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
  • Just noted, there is no Float type, and the Int type aliases for Int64 on all modern 64-bit CPUs. See also Inquiring for subtype() and supertype() functions. However…
  • Function arguments that want to work with Integers and Floats use Real. Function arguments that work only with Floats use AbstractFloat. Function arguments that work only with Ints use Integer.
  • Typed variables can be uninitizialized (contain garbage), but they usually cannot have empty (or missing or Null) content choice—to facilitate such requires explicit retyping of the variable to allow an empty or 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.

Speed of Primitive Numeric Types

Most users will be on later-model Intel CPUs. On a Xeon i7-7700, I ran a simple test program:

snippet.text
[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
  • Not Shown: Unsigned types are often 10-20% slower.
  • 32-bit numerics are Intel's sweet spot.
  • Int128 is notably slower among Ints.
  • Float32 is fastest, but Float64 is “only” 50% slower.
  • Float16 and BigFloat are best avoided unless absolutely necessary. For all but the most trivial calculations, it is faster when a program converts Float16 to Float32, calculates in Float32, and then converts back (compared to calculating natively in Float16). (Here, this would take 31ms.)
  • If you need both speed and BigFloat precision, either find a new computer architecture or a priest.

Missing Types and missing Values

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.)

Testing Whether a Type is a Number (Int, Float, etc.)

Identifying Type

Already discussed in Inquiring, typeof() is the key function. The content eltype (“element-type”) works for both scalars and for the contents of arrays:

snippet.juliarepl
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
  • PS: Vectors and arrays will be explained in Arrays:

Testing Within Numerical Type Hierarchy

snippet.juliarepl
julia> eltype( 1 ) <: Number
true

julia> eltype( 2 ) <: Real
true

julia> eltype( 1.0 ) <: Int
false

Testing Mixed Missing-Number Vectors

Warning: This does not work when the type is a union that includes support for missings.

snippet.juliarepl
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:

snippet.juliarepl
julia> eltype( [ 1, missing ] ) <: Union{Missing,Int64}			## standard test
true

julia> eltype( collect(skipmissing( [ 1, missing, 3 ] )) ) <: Int64	## or remove missings first
true

Value and Value-Type Comparisons

Use == for value comparisons. Use === for value plus type plus memory-location comparisons:

snippet.juliarepl
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

Numeric Type Conversions

snippet.juliarepl
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)
snippet.juliarepl
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']"

Floats

  • Long numbers can be entered with underscores for visual help: 1_000_000.123_456.
  • Converting a string to a Float64 is parse(Float64, "12.2"), not Float64("12.2").

Range of Allowed Numeric Values (typemax, eps, etc.)

snippet.juliarepl
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 negative32768

The smallest (tiniest) value above zero that is representable,

FIXME 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

snippet.juliarepl
julia> eps(Float64)
2.220446049250313e–16
  • Obscure: nextfloat gives the next representable float. RoundNearest can inform IEEE-754 operation.

IEEE NaN

snippet.juliarepl
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.

Float to Int Conversion

To round a Float64 to Int:

snippet.juliarepl
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:

Truncation to Integer

round to nearest integer
floor towards -Inf
ceil towards +Inf
trunc towards 0
snippet.juliarepl
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
  121213

Float to Integral/Fractional Part

snippet.juliarepl
julia> modf(12.312)		## fraction comes first, then integer part (but still a Float)
(0.3119999999999994, 12.0)

Rounding Floating-Point Numbers

snippet.juliarepl
julia> round(123.4567; digits=2)          ## same operation as Float->Int
123.46
  • There are also further such functions for division/modules.

Rounding To N Significant Digits

snippet.juliarepl
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

Rounding To Nearest X (typically 5*10^x)

snippet.juliarepl
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

Comparing Floating-Point Numbers

The usual caveats on floating-point precision apply:

snippet.juliarepl
julia> isequal( (sqrt(5))^2 , 5 )
false

because

snippet.juliarepl
julia> sqrt(5)^2
5.000000000000001

Rough Comparing Floating-Point Numbers

Instead, use isapprox, which is in UTF the operator:

snippet.juliarepl
julia> isapprox( (sqrt(5))^2 , 5.0 )
true

julia> isapprox( (sqrt(5))^2 , 5.0, atol=0.0001 )
true

Printing Commas and Periods in Numbers (Commify-ing / Pretty-Printing)

snippet.juliarepl
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   "
  • this also works for arrays and ranges: sprintf1.("%.3f", 1:3) and sprintf1.("%.3f", [ 1 2 ; 3 4])

Printf and Sprintf

C-style printf and sprintf work as macros, so they require '@' prefixes:

snippet.juliarepl
julia> using Printf

julia> @printf("%12.5f", pi)
     3.14159
  • this also works for arrays and ranges: sprintf1.("%.3f", 1:3) and sprintf1.("%.3f", [ 1 2 ; 3 4])

Other Float Functions

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

Integers and Integer-related Types

There are '=' operators that do '' and then assign the result, such as x+=1 which adds 1 to x.

Integer Notes

  • Long numbers can be entered with underscores for visual help: 1_000_000.
  • Integer overflows are not trapped. 2^64 gives zero without warning.
  • Converting string to int is parse("12"), not Int("12")

Integer Division

snippet.juliarepl
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

Division by Zero

snippet.juliarepl
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.

Converting to a Different Base

snippet.juliarepl
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.

Numeric Literals (Implied Multiplication)

snippet.juliarepl
julia> const x= 3; 2x
6

julia> 2^2x   ## warning: not 4*3=12, but 2^6.
64

Iterators

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.

Iterating Over All Combinations / Permutations / etc.

snippet.juliarepl
julia> for i=0:(2^31); 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]

All Permutations

snippet.juliarepl
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']

Enumerations and Categories

Compile Time

snippet.juliarepl
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)

Run Time: Categorical Vectors

To convert integer vectors into categoricals vectors:

snippet.juliarepl
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'

Not Discussed

A number of features were not discussed.

  • Julia has full support for rational numbers (e.g., via //).
  • Julia has full support for complex numbers.
  • Julia has the full C complement of bitwise operators: ~, &, |, <<, >>, >>>, xor.
  • Julia has full support for Booleans and for BitVectors

Backmatter

Useful Packages on Julia Repository

Notes

  • Unfortunately, it is not possible to turn on a compiler warning whenever an uninitialized variable is used before assignment. (x= Vector{Float64}(undef,3); y= x[1]+2.)
  • The above has not (yet) discussed constructs such as 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.
  • See also structs for an example of how to define a limited-range numeric type.
  • The NaNMath package traps some function exceptions, returning zero.

References

numbers.txt · Last modified: 2018/11/22 20:48 (external edit)