Monday, March 29, 2021

Numeric Types

In the last post we saw variables that can hold integers and floating point (decimal) numbers. Julia has other numeric types, and we'll see that these types are arranged in a hierarchy.

Integers and Floating Point Numbers

As review, here's how you store an integer into the variable x and a floating point number into the variable y

In [1]:
Out[1]:
358
In [2]:
Out[2]:
78.25155

Complex Numbers

Julia has built-in support for complex numbers - we can create them, store them into variables, and perform calculations with them.

In [3]:
Out[3]:
3 + 4im
In [4]:
Out[4]:
3
In [5]:
Out[5]:
4
In [6]:
Out[6]:
-12 + 5im
In [7]:
Out[7]:
-9 + 9im
In [8]:
Out[8]:
-56 - 33im
In [9]:
Out[9]:
-0.09467455621301774 - 0.37278106508875736im
In [10]:
Out[10]:
5.0
In [11]:
Out[11]:
0.9272952180016122
In [12]:
Out[12]:
3 - 4im
In [13]:
Out[13]:
25 + 0im
In [14]:
Out[14]:
-2 + 0im
In [15]:
Out[15]:
-1.0 + 1.2246467991473532e-16im
In [16]:

Rational Numbers

Rational numbers are entered like this: 3//4 for the fraction 3/4.

In [17]:
Out[17]:
3//4
In [18]:
Out[18]:
27//20
In [19]:
Out[19]:
137//60
In [20]:
Out[20]:
3//4
In [21]:
Out[21]:
9//16
In [22]:
Out[22]:
3//1

Numeric Type Hierarchy

These types, integer, real, complex, and rational, are called... numeric types. Imagine that.

In [23]:
Out[23]:
Int64
In [24]:
Out[24]:
Float64
In [25]:
Out[25]:
Complex{Int64}
In [26]:
Out[26]:
Rational{Int64}

The "64" in the results indicates how many bits are used to store that number. So, x is an integer and takes up 16 bits of memory, etc.

As it goes, these numeric types form a hierachy - a structure that looks like an upside-down tree - that our code can utilize within Julia. This doesn't sound too interesting in itself, but it will turn out to be extremely useful several chapters from now!

In [27]:
Out[27]:
Signed
In [28]:
In [29]:
Out[29]:
AbstractFloat
In [30]:
Out[30]:
Real
In [31]:
Out[31]:
Number
In [32]:
Out[32]:
Any
In [33]:
Out[33]:
Any

There's nothing above Any, we've reached the top of the hierarchy. This is not only the top of the numerical type hierarchy, but the top of all Julia types, including non-numeric values like strings, booleans, etc.

If we were to repeat this process of repeatedly calling supertype on Int64, we will eventually get to Any. Same for Complex{Int64} and Rational{Int64}.

So supertype takes us up the type hierarchy. To see what is below a given type, use the subtypes function.

In [34]:
Out[34]:
510-element Vector{Any}:
 AbstractArray
 AbstractChannel
 AbstractChar
 AbstractDict
 AbstractDisplay
 AbstractMatch
 AbstractPattern
 AbstractSet
 AbstractString
 Any
 Base.AbstractBroadcasted
 Base.AbstractCartesianIndex
 Base.AbstractCmd
 ⋮
 Tuple
 Type
 TypeVar
 UndefInitializer
 Val
 Vararg
 VecElement
 VersionNumber
 WeakRef
 ZMQ.Context
 ZMQ.Socket
 ZMQ._Message

Wow, that's a lot of types, the majority of which we'll never directly use! Let's start from a type that's one step under Any, the Number type. BTW, Number would be in the list of subtypes of Any, but the output got truncated.

In [35]:
Out[35]:
2-element Vector{Any}:
 Complex
 Real

That's interesting, there are two types under Number - and they are not arranged like mathematics tells us! As it goes, this is done for memory efficiency.

In [36]:
Out[36]:
4-element Vector{Any}:
 AbstractFloat
 AbstractIrrational
 Integer
 Rational
In [37]:
Out[37]:
3-element Vector{Any}:
 Bool
 Signed
 Unsigned
In [38]:
Out[38]:
6-element Vector{Any}:
 BigInt
 Int128
 Int16
 Int32
 Int64
 Int8
In [39]:
Out[39]:
Type[]
In [40]:

There is much more to say about Julia type hierarchy, but that'll take us too far afield.

Here's what the numeric type hierarchy looks like in total (image from Wikipedia):

Type Inference

If you've studied languages like C or Java, the thing that should stand out is that we've not specified which types of values a variable can hold. This is mostly unnecessary, as the Julia compiler is very good at "type inference" - figuring out what type of value a variable can hold.

When the Julia compiler makes a mistake, that can lead to performance problems as well as our programs using more memory is necessary!

Here's a blatent example of how types can change:

In [41]:
Out[41]:
5472
In [42]:
Out[42]:
Int64
In [43]:
Out[43]:
"Hello, World!"
In [44]:
Out[44]:
String

If the C or Java compilers would let us get away with making p hold a string, then they would surely complain when we made it hold an integer!

Here's a more subtle example of type instability:

In [45]:
Out[45]:
50
In [46]:
Out[46]:
Int64
In [47]:
Out[47]:
52.0
In [48]:
Out[48]:
Float64

We will see later how to specify what types of values a variable can hold.

Getting a List of Variables

A list of all variables and the types of values they contain is very convenient, especially in long notebooks or in the REPL. Here's how to get that list:

In [49]:
Out[49]:
name size summary
Base Module
Core Module
Main Module
p 21 bytes 13-codeunit String
q 8 bytes Float64
r1 16 bytes Rational{Int64}
x 8 bytes Int64
y 8 bytes Float64
z1 16 bytes Complex{Int64}
z2 16 bytes Complex{Int64}

The first three items on that list, Base, Core, and Main, are the "modules" (code libraries) that are automatically available to you when you code in Julia. We will ignore them for now, though we have implicitly used all of them.

Some IDEs, like Atom and VS Code, show this list as part of the user interface. Those IDEs are calling varinfo() in the background.

Also notice that the size of the values stored in the variables are reported in bytes instead of bits. There are 8 bits in a byte, and that's why a 64-bit integer (Int64) is shown to use 8 bytes.

Conclusion

We covered the following topics in this post:

  • Complex and Rational values
  • Determining the type of a value using the typeof() function
  • Julia's type hierarchy, and the Any type at the top
  • How to use supertype() and subtypes() to explore this hierarchy
  • Type inference
  • Type instability
  • Using varinfo() to get a list of currently-defined variables.

Notice the language used in discussing types: a value has a type - and a variable takes on the type when that value is assigned to it.

In [ ]:

No comments:

Post a Comment