2023-11-06
We can use the Julia REPL as a scientific calculator to perform simple arithmetic. Although this kind of interactive use employs only a small fraction of Julia’s capabilities, it is the natural place to start before considering more advanced topics on vectors, functions and plotting in later lessons.
In this lesson, you will learn to evaluate arithmetic expressions in Julia and to understand the output, particularly when scientific notation, complex numbers and infinite or undefined answers are involved.
After completing the lesson, you should know
Inf (infinity) and NaN
(not-a-number).Although Julia’s arithmetic is reasonably intuitive, certain intrinsic limitations of digital computers lead to some subtleties that can be confusing at first. Consequently, this lesson is quite long.
The Julia symbol for each of the five main arithmetic operations is shown in the following table.
| Operator | Symbol |
|---|---|
| Addition | + |
| Subtraction | - |
| Multiplication | * |
| Division | / |
| Exponentiation | ^ |
In an arithmetic expression, the order of precedence is as follows.
Operations with the same precedence are evaluated from left to right,
except for exponentiation (^). For example,
a / b * c evaluates as (a/b)*c, whereas
a^b^c evaluates as a^(b^c).
Try out the following examples. In each case, first apply the precedence rules to calculate the answer yourself, and then type the expression in the Julia REPL to check if you are correct.
1 + 2*3
(1+2)*3
4/2 + 1
1 + 4/2
4/(2+1)
8^2 / 3
8^(2/3)
12/2/3
12/(2*3)
4^3^2
4^(3^2)
(4^3)^2
2*-3
2^-3
In the last two expressions, the minus sign is interpreted as a
unary rather than a binary operator. The unary minus
has a higher precedence than * and ^ so the
results are 2*(-3) and 2^(-3).
In practice, if you are unsure about how Julia will evaluate an expression, it is best to use explicit parentheses. Doing so will make your code easier to read.
Exercise. Type a command to evaluate the fraction
with numerator 17.1+20.3 and denominator
36.5+41.8. Hint: the answer is not
17.1 + 20.3/36.5 + 41.8
Every Julia object has a type that can be found using the
typeof function. For example,
typeof(3)
returns the value Int64, whereas
typeof(3.0)
returns the value Float64. On any 64-bit system, these
are the default integer and floating-point data types that the
arithmetic registers in the CPU operate upon. Julia has a rich hierarchy
of types. The most general type is Any, which forms the
root of the tree of types, that is, every other type is a subtype of
Any. For example, the Number type is an
immediate subtype of Any, and is a supertype of every kind
of number in Julia, as shown below.
In these lessons, we will mostly deal just with Int64
and Float64 numbers, and occasionally with
Complex, Rational and Irrational
numbers.
Exercise. Use the subtype and
supertype functions to explore the type hierarchy.
As the name suggests, an Int64 is stored using 64 bits,
or equivalently, using 8 bytes. The typemax and
typemin functions give the largest and smallest numbers
that can be stored in a given data type, with
typemax(Int64)
typemin(Int64)
showing that the largest and smallest Int64 numbers are
and
Exercise. What are the largest and smallest
Int8 numbers?
Exercise. The bitstring function
returns a string of zeros and ones showing the binary representation of
given number. Look at the output of bitstring(n) if
n is typemin(Int8),
typemax(Int8), Int8(-1), Int8(0)
and Int8(1).
The arithmetic registers in the CPU actually perform integer arithmetic modulo with the result in the interval . Thus, the result of
typemax(Int64) + 1
is the negative number typemin(Int64).
For any real number, we can “float” the decimal point places to the left or right by incorporating a factor of or . For example, Any positive real number has a unique representation as a normalised floating-point number, that is, as a product where and is an integer. The number is called the mantissa (or significand), and is called the exponent.
We can use exponential format, also called scientific
format, when entering or displaying floating-point numbers. For
example, Julia will display
as 2.3e17, and display
as 7.5e-8. This format is useful whenever you have to deal
with very large or very small numbers.
For human convenience, programming languages like Julia display floating-point numbers in base 10, but the computer hardware works in binary, so the actual floating-point representation is with . A standard known as IEEE 754 defines the binary format for 64-bit floating-point numbers: first is the sign bit ( for positive and for negative), next are 11 bits for storing the exponent , and finally 52 bits for storing the mantissa . A machine number is one that can be represented exactly in this format.
The function floatmax and floatmin return
the largest and smallest positive (finite) numbers for any subtype
T of AbstractFloat. Try the following
examples.
floatmax(Float64)
floatmin(Float64)
floatmax(Float32)
floatmin(Float32)
Strictly speaking, floatmin() gives the smallest
normalised positive number. It is possible to represent smaller
values by allowing denormalised numbers, that is, by allowing a
mantissa smaller than
.
Of particular importance is the function eps() that
returns the machine epsilon, defined as the smallest positive
number
such that the rounded value of
is strictly greater than
.
Thus, if
is any positive machine number strictly less than
,
then Julia will evaluate
as
.
You can verify that eps(Float64) is
.
The practical consequence is that for the result of any floating-point
calculation we should not expect accuracy better than about 15
correct significant decimal figures.
The function call typemax(Float64) returns
Inf, a special value that represents ‘infinity’. Likewise,
typemin(Float64) returns -Inf.
Julia evaluates the expression
1.0/0.0
as Inf, and similarly evaluates -1.0/0.0 as
-Inf. More interesting perhaps, is that expressions
involving Inf can yield finite results. For example, try
the following in the REPL.
atan(Inf)
tahn(-Inf)
cos(1/Inf)
However, some arithmetic expressions cannot be given any meaningful value, either finite or infinite. For example, try the following.
0.0/0.0
Inf - 2*Inf
In both cases, the result is NaN, which stands for
Not-a-Number. There is nothing magical about
Inf and NaN: they are just special bit
patterns that you can look at by calling bitstring(Inf) and
bitstring(NaN).
Exercise. What is the result of the following function calls?
sin(Inf)
log(-1.0)
The binary representation of an integer is different from the binary
representation of the equivalent floating-point number, as you can
easily observe by comparing bitstring(5) with
bitstring(5.0). Nevertheless, if you combine integer and
floating-point values in an arithmetic expression, then Julia will
promote each Int64 to its corresponding
Float64 value to obtain a purely floating-point expression.
For example, 3 + 1/2 becomes 3.0 + 1.0/2.0
which evaluates to 3.5.
Notice that division is unusual as a binary operation, because the
result a/b can have a different type from the operands
a and b. If a and b
are of type Int64, then a/b is of type
Float64 even if a/b is a whole number. Julia
provides a function div for integer division. For
example,
div(7, 2)
returns 3. The divrem function returns both
the quotient and remainder; thus,
q, r = divrem(7, 2)
gives q=3 and r=1.
The // operator is used to construct
Rational numbers. Doing
x = 3//4
creates a number x of type Rational{Int64},
which is really just a pair of integers, the numerator and denominator,
that can be referenced as x.num and x.den,
respectively. Julia automatically cancels any common factors, so for
instance after doing
y = 7 // 21
we find that y equals 1//3. Rational
arithmetic works as expected; thus,
5//13 - 19//2 + 7
evaluates to -55//26.
Just as a Rational number consists of a numerator and
denominator, a Complex consists of a real and an imaginary
part. For example, if
z = 3.2 - 7.2im
then the real part, referenced as z.re, is
3.2, and the imaginary part, referenced as
z.im, is -7.2. You could also construct
z using the complex function:
z = complex(3.2, 7.2)
The abs and angle functions give the
modulus and argument (phase) of a complex number. Note that doing
w = 3 + 5im
makes w of type Complex{Int64}, that is, a
complex number with integer real and imaginary parts.
Exercise. What type is im? What type is
im^2?
In this lesson, you have learned about
Inf and
NaN;The Julia documentation includes a section on Integers and Floating-Point Numbers. A classic paper is David Golberg, What every computer scientist should know about floating-point arithmetic, ACM Computing Surveys, Volume 23, pp 5-48, 1991.