The programming language Julia
(www.julialang.org) is a new language that builds on a long history of so-called dynamic scripting languages (DSLs). DSLs are widely used for exploratory work and are part of the toolbox of most all data scientists, a rapidly growing area of employment. The language Julia
is reminiscent of is MATLAB
, though it offers many improvements over that language in terms of ease of use and speed in some cases. (Well it should, MATLAB
was started back in the 70s.) Even better, julia
is an open-source project which means it is free to download and install, unlike the commercial package MATLAB
.
This class will use Julia
to explore calculus concepts. Unlike some other programs used with calculus (e.g., Mathematica, Maple, and Sage) Julia
is not a symbolic math language. Rather, we will use a numeric approach. This gives a different viewpoint on the calculus material supplementing that of the text book. Though there are a few idiosyncrasies we will see along the way, for the most part the Julia
language will be both powerful and easy to learn.
In this project we start with baby steps -- how to use julia
to perform operations we can do on the calculator.
Julia
can replicate the basics of a calculator with the standard notations. The familiar binary operators are +
, -
, *
, /
, and ^
. You basically type in the expression, and then press the enter or return key.
For example, to add two and two:
2 + 2
## 4
Answers are typeset with leading comment signs (##
) in these notes, but that isn't how they appear at the julia
command line. We use comment signs to separate the input from the output in such a way that the input commands can be easily copy and pasted into julia
.
Or to convert \(70\) degrees to Celsius with the standard formula \(C=5/9(F-32)\):
(5/9)*(70 - 32)
## 21.11111111111111
Of to find a value of \(32 - 16x^2\) when \(x=1.5\):
32 - 16*(1.5)^2
## -4.0
To find the value of \(\sqrt{15}\) we can use power notation:
15^(1/2)
## 3.872983346207417
Compute \(22/7\) in julia
.
Compute \(\sqrt{220}\) in julia
.
Compute \(2^8\) in julia
.
The order of operations are conventions used to decide which operation will happen first, when there is a choice of more than one. A simple example, is what is the value of \(3 \cdot 2 + 1\)?
There are two binary operations in this expressions: a multiplication and an addition. Which is done first and which second?
In some instances it doesn't matter. A case of this would be \(3 + 2 + 1\). This is because addition is associative. In the case \(3 \cdot 2 + 1\) it certainly matters.
For \(3 \cdot 2 + 1\) if we did the addition first, the expression would \(9 = 3\cdot 3\). If we did the multiplication first, the value would be \(7=6+1\). In this case, we all know that the correct answer is \(7\), as we perform multiplication before addition, or in more precise terms the precedence of multiplication is higher than that of addition.
The standard order of the basic mathematical operations is remembered by many students through the mnemonic PEMDAS, which can be misleading, so we spell it out here:
P
) First parenthesesE
) then exponents (or powers)MD
) then multiplication or divisionAS
) then addition or subtraction.This has the precedence of multiplication (MD
) higher than that of subtraction (AS
), as just mentioned.
Applying this, if we have the mathematical expression
\[ \frac{12 - 10}{2} \]
We know that the subtraction needs to be done before the division, as this is how we interpret this form of division. How to make this happen? The precedence of parentheses is used to force the subtraction before the division, as in (12-10)/2
. Without parentheses you get a different answer:
(12 - 10)/2
## 1.0
12 - 10/2
## 7.0
Parentheses are used to force lower precedence operations to happen before higher precedence ones.
There is a little more to the story, as we need to understand what happens when we have more then one operation with the same level. For instance, what is \(2 - 3- 4\)? Is it \((2 - 3) - 4\) or \(2 - (3 - 4)\).
Unlike addition, subtraction is not associative so this really matters. The subtraction operator is left associative meaning the evaluation of \(2 - 3 - 4\) is done by \((2-3) - 4\). The operations are performed in a left-to-right manner. Most -- but not all operations -- are left associative, some are right associative and performed in a right-to-left manner.
right to left It is the order of which operation is done first, not reading from right to left, as one might read Arabic.
To see that julia
has left associative subtraction, we can just check:
2 - 3 - 4
## -5
(2 - 3) - 4
## -5
2 - (3 - 4)
## 3
Not all operations are processed left-to-right. The power operation, ^
, is right associative, as this matches the mathematical usage. For example:
4^3^2
## 262144
(4^3)^2
## 4096
4^(3^2)
## 262144
What about the case where we have different operations with the same precedence? What happens then? A simple example would be \(2 + 3 - 4\)? Is this done in a left to right manner as in:
(2 + 3) - 4
## 1
Or a right-to-left manner, as in:
2 + (3 - 4)
## 1
And the answer is left-to-right:
2 + 3 - 4
## 1
Wich of the following is a valid julia
expression for
\[ \frac{3 - 2}{4 - 1} \]
that uses the least number of parentheses?
Wich of the following is a valid julia
expression for
\[ \frac{3\cdot2}{4} \]
that uses the least number of parentheses?
Wich of the following is a valid julia
expression for
\[ 2^{4 - 2} \]
that uses the least number of parentheses?
One of these three expressions will produce a different answer, select that one:
One of these three expressions will produce a different answer, select that one:
One of these three expressions will produce a different answer, select that one:
Compute the value of
\[ \frac{9 - 5 \cdot (3-4)}{6 - 2}. \]
Compute the following using julia
:
\[ \frac{(.25 - .2)^2}{(1/4)^2 + (1/3)^2} \]
Compute the decimal representation of the following using julia
:
\[ 1 + \frac{1}{2} + \frac{1}{2^2} + \frac{1}{2^3} + \frac{1}{2^4} \]
Compute the following using julia
:
\[ \frac{3 - 2^2}{4 - 2\cdot3} \]
Compute the following using julia
:
\[ (1/2) \cdot 32 \cdot 3^2 + 100 \cdot 3 - 20 \]
Most all calculators used are not limited to these basic arithmetic operations. So-called scientific calculators provide buttons for many of the common mathematical functions, such as exponential, logs, and trigonometric functions. Julia
provides these too, of course.
There are special functions to perform common powers. For example, the square-root function is used as:
sqrt(15)
## 3.872983346207417
This shows how to evaluate a function -- using its name and parentheses, as in function_name(arguments)
. Parentheses are also used to group expressions, as would be done to do this using the power notation:
15^(1/2)
## 3.872983346207417
Additionally, parentheses are also used to make "tuples", a concept we don't pursue here but that is important for programming with julia
. The point here is the context of how parentheses are used is important, though for the most part the usage is the same as their dual use in your calculus text.
There is also a cube-root function:
cbrt(27)
## 3.0
These two functions are not exactly the same as using ^
, as they differ when the inputs are not in their domain:
sqrt(-1)
## "DomainError()"
(-1)^(1/2) ## NaN is "not a number"
## NaN
Here both give no answer -- though differently, as opposed to the value of imaginary value \(i\) (or im
in julia
). For cube roots, the difference is different:
cbrt(-8) ## correct
## -2.0
(-8)^(1/3) ## need first parentheses, why?
## NaN
[ The reason for the issues with ^
is due to input being real, but the definition wants to return a complex value. Try: (-8 + 0im)^(1/3)
. ]
The basic trigonometric functions in julia
work with radians:
sin(pi/4)
## 0.7071067811865475
cos(pi/3)
## 0.5000000000000001
But students think in degrees. What to do? Well, you can always convert via the ratio \(\pi/180\):
sin(45 * pi/180)
## 0.7071067811865475
cos(60 * pi/180)
## 0.5000000000000001
However, julia
provides the student-friendly functions sind
, cosd
, and tand
to work directly with degrees:
sind(45)
## 0.7071067811865476
cosd(45)
## 0.7071067811865476
The values \(e^x\) can be done with the built-in constant e
:
e^2
## 7.38905609893065
Or through the function exp(x)
:
exp(2)
## 7.38905609893065
As, e
can be redefined, it is best to use the latter style, though it takes a bit more typing.
The logarithm function, log
does log base \(e\):
log(exp(2))
## 2.0
To do base 10, one can specify it as the first argument:
log(10, exp(2))
## 0.8685889638065035
Or use the function log10
.
There are some other useful functions For example, abs
for the absolute value, round
for rounding, floor
for rounding down and ceil
for rounding up. Here are some examples
round(3.14)
## 3.0
floor(3.14)
## 3.0
ceil(3.14)
## 4.0
The observant eye will notice the answers above are not integers. (How to tell?) What to do if you want an integer? These functions have versions iround
, ifloor
, and iceil
to return integer values. The thinking here is that functions should generally return values in the type of the variable input and when these are given floating point values, they return floating point values. (In general, floating point values can be converted to integers through integer
.)
What is the value of \(\sin(\pi/10)\)?
What is the value of \(\sin(52^\circ)\)?
Is \(\sin^{-1}(\sin(3\pi/2))\) equal to \(3\pi/2\)?
What is the value of round(3.5000)
What is the value of sqrt(32 - 12)
Which is greater \(e^\pi\) or \(\pi^e\)?
What is the value of \(\pi - (x - \sin(x)/\cos(x))\) when \(x=3\)?
Search the page mathematical functions for a function which finds the factorial of n
. The proper julia
command to find \(10!\) would be:
With a calculator, one can store values into a memory for later usage. This useful feature with calculators is greatly enhanced with computer languages, where one can bind, or assign, a variable to a value. For example the command x=2
will bind x
to the value \(2\):
x = 2
## 2
So, when we evaluate
x^2
## 4
The value assigned to x
is looked up and used to return \(4\).
The word "dynamic" to describe the Julia
language refers to the fact that variables can be reassigned and retyped. For example:
x = sqrt(2) ## a Float64 now
## 1.4142135623730951
In julia
one can have single letter names, or much longer ones, such as
some_ridiculously_long_name = 3
## 3
some_ridiculously_long_name^2
## 9
The basic tradeoff being: longer names are usually more expressive and easier to remember, whereas short names are simpler to type. To get a list of the currently bound names, the whos
function may be called. Not all names are syntactically valid, for example names can't begin with a number or include spaces.
To work with computer languages, it is important to appreciate that the equals sign in the variable assignment is unlike that of mathematics, where often it is used to indicate an equation which may be solved for a value. With the following computer command the right hand expression is evaluated and that value is assigned to the variable. So,
x = 2 + 3
## 5
does not assign the expression 2 + 3
to x
, but rather the evaluation of that expression, which yields 5. (This also shows that the precedence of the assignment operator is lower than addition, as addition is performed first in the absence of parentheses.)
At the prompt, a simple expression is entered and, when the return key is pressed, evaluated. At times we may want to work with multiple subexpressions. A particular case might be setting different parameters:
a=0
## 0
b=1
## 1
Can be more tersely written by separating each expression using a semicolon:
a=0; b=1;
Note that julia
makes this even easier, as one can do multiple assignments via "tuple destructuring:"
a, b = 0, 1 ## printed output is a "tuple"
## (0,1)
a + b
## 1
Finally, there are begin-end
blocks to group expressions:
begin
a=0
b=1
end
## 1
There are many other julia
idioms that begin with some keyword, like begin
, and terminate with end
.
Let \(a=10\), \(b=2.3\), and \(c=8\). Find the value of \((a-b)/(a-c)\).
What is the answer to this computation?
a = 3.2; b=2.3
a^b - b^a
For longer computations, it can be convenient to do them in parts, as this makes it easier to check for mistakes. (You likely do this with your calculator.)
For example, to compute
\[ \frac{p - q}{\sqrt{p(1-p)}} \]
for \(p=0.25\) and \(q=0.2\) we might do:
p, q = 0.25, 0.2
top = p - q
bottom = sqrt(p*(1-p))
ans = top/bottom
What is the result of the above?
Unlike a calculator, julia
does not treat all numbers equally. Calculators do only floating point manipulations, whereas julia
has types for many different numbers: Integer
, Real
, Rational
, Complex
, and specializations depending on the number of bits that are used, e.g., Int64
and Float64
. For the most part there is no need to think about the details, as values are promoted to a common type when used together. However, there are times where one needs to be aware.
In the real number system of mathematics, there are the familiar real numbers and integers. The integers are viewed as a subset of the real numbers.
Julia provides types Integer
and Real
to represent these values. (Actually, the Integer
type represents more than one actual storage type, either Int32
or Int64
.) These are separate types. The type of an object is returned by typeof()
.
For example, the integer \(1\) is simply created by the value 1
:
typeof(1)
## Int64
The floating point value \(1\) is specified by using a decimal point:
typeof(1.0)
## Float64
The two values 1
and 1.0
are not the same -- they are stored differently. However, in most -- but not all -- uses they can be used interchangeably.
When a computer is used to represent numeric values there are limitations: a computer only assigns a finite number of bits for a value. This works great in most cases, but since there are infinitely many numbers, not all possible numbers can be represented on the computer.
The first limitation is numbers can not be arbitrarily large.
Take for instance a 64-bit integer. A bit is just a place in computer memory to hold a \(0\) or a \(1\). Basically one bit is used to record the sign of the number and the remaining 63 to represent the numbers. This leaves the following range for such integers \(-2^{63}\) to \(2^{63} - 1\).
Julia
is said to not provide training wheels. This means it doesn't put in checks for integer overflow, as these can slow things down. To see what happens, let just peek:
2^62 ## about 4.6 * 10^18
## 4611686018427387904
2^63 ## negative!!!
## -9223372036854775808
So if working with really large values, one must be mindful of the difference -- or your bike might crash!
Gotchas
Look at the output of
2^3^4
## 0
Why is it 0? The value of \(3^4=81\) is bigger than 63, so \(2^{81}\) will overflow.
The following works though:
2.0 ^ 3 ^ 4
## 2.4178516392292583e24
This is because the value 2.0
will use floating point arithmetic which has a much wider range of values. (The julia
documentation sends you to this interesting blog post johndcook, which indicates the largest floating point value is \(2^{1023}(2 - 2^{-52})\) which is roughly 1.8e308
.
Scientific notation is used to represent many numbers
A number in julia
may be represented in scientific notation. The basic canonical form is \(a*10^b\), with \(-10 < a < 10\) and \(b\) is an integer. This is written in julia
as aeb
where e
is used to separate the value from the exponent. The value 1.8e308
means \(1.8 \cdot 10^{308}\). Scientific notation makes it very easy to focus on the gross size of a number, as the exponent is set off.
The second limitation is numbers are often only an approximation.
This means expressions which are mathematically true, need not be true once approximated on the computer. For example, \(\sqrt{2}\) is an irrational number, that is, its decimal representation does not repeat the way a rational number does. Hence it is impossible to store on the computer an exact representation, at some level there is a truncation or round off. This will show up when you try something like:
2 - sqrt(2) * sqrt(2)
## -4.440892098500626e-16
That difference of basically \(10^{-16}\) is roughly the machine tolerance when representing a number. (One way to imagine this is mathematically, we have two ways to write the number \(1\):
\[ 0.\bar{9} = 0.9999... = 1 \]
but on the computer, you can't have the "..." in a decimal expansion -- it must truncate -- so instead values become something like \(0.9999999999999999\). Which are really close to \(1\) but are not precisely \(1\).
Comparing values
A typical expression in computer languages is to use ==
to compare the values on the left- and right-hand sides. This is not assignment, rather a question. For example:
2 == 2
## true
2 == 3
## false
sqrt(2) * sqrt(2) == 2 ## surprising?
## false
The last one would be surprising were you not paying attention to the last paragraph. Comparisons with ==
work well for integers and strings, but not with floating point numbers. For these the isapprox
function can be used:
isapprox(sqrt(2) * sqrt(2), 2)
## true
This function basically compares whether \(|x-y| < tol\) where \(tol\) is some tolerance related to the machine precision. Machine precision is given by eps()
.
Comparisons do a promotion prior to comparing, so even though these numbers are of different types, the ==
operation treats them as equal:
1 == 1.0
## true
The ===
operator has an even more precise notion of equality:
1 === 1.0
## false
The special floating-point values:
NaN
,Inf
Floating point contains two special values: NaN
and Inf
to represent "not a number" and "infinity." These arise in some natural cases:
1/0 ## infinity. Also -1/0.
## Inf
0/0 ## indeterminate
## NaN
These values can come up in unexpected circumstances. For example division by \(0\) can occur due to round off errors:
x = 1e-17
## 1.0e-17
x^2/(1-cos(x)) ## should be about 2
## Inf
In addition to special classes for integer and floating point values, Julia
has a special class for rational numbers, or ratios of integers. To distinguish between regular division and rational numbers, julia
has the //
symbol to define rational numbers:
1//2
## 1//2
typeof(1//2)
## Rational{Int64}
As you know, a rational number \(m/n\) can be reduced to lowest terms by factoring out common factors. Julia
does this to store its rational numbers:
2//4
## 1//2
Rational numbers are used typically to avoid round off error when using floating point values. This is easy to do, as julia
will convert them when needed:
1//2 - 5//2 ## still a rational
## -2//1
1//2 - sqrt(5)/2 ## now a floating point
## -0.6180339887498949
However, we can't do the following, as the numerator would be non-integer when trying to make the rational number:
(1 - sqrt(5)) // 2
## "MethodError(//,(-1.2360679774997898,2))"
As mentioned, one can write 3e8
for \(3 \cdot 10^8\), but in fact to julia
the two values 3e8
and 3*10^8
are not quite the same, as one is stored in floating point, and one as an integer:
typeof(3e8)
## Float64
typeof(3 * 10^8)
## Int64
One can use 3.0 * 10^8
to get a floating point equivalent. (The product of the integer 10^8
is promoted to floating point when multiplied.)
Compute the value of \(2^{1023}(2 -2^{-52})\) using 2.0
-- not the integer 2
:
The result of sqrt(16)
is
The result of 16^2` is
The result of 1/2
is
The result of 2/1
is
Which number is 1.23e4
?
Which number is -43e-2
?
What is the answer to the following:
ans = round(3.4999999999999999);
If you need more bits, julia
provides the BigInt
and BigFloat
classes which give \(256\) bits of precision. Using this allows one to compute \(2^3^4\) precisely as an integer:
x = BigInt(2)
ans = x^3^4
What is the answer?