# Repeated evaluation: for-loops

## Contents

# 1.4. Repeated evaluation: for-loops¶

Suppose we want to compute the finite sum \(s_n = \sum_{i=1}^n \frac{1}{i}\) for some integer \(n\ge 1\). To do this in a computer code, we need a *loop*, which runs a block of code repeatedly for a given number of times.

The simplest form of a *for-loop* has the syntax

```
for i = 1:n
# This code will be repeated n times, for i = 1,2,...,n
end
```

Using for-loops, we can write a function to compute \(s_n\):

```
function compute_s(n)
sn = 0
for i = 1:n
sn += 1/i
end
sn
end
```

```
compute_s (generic function with 1 method)
```

and use it to, e.g., compute \(s_{100}\):

```
compute_s(100)
```

```
5.187377517639621
```

More generally, a for-loop can have the syntax

```
for x = start:step:stop
# Code to be repeated
end
```

This will repeat the code inside the for-loop, with `x`

beginning at the value `start`

, increasing by `step`

for each repetition, and end at or before `x`

reaches the value `stop`

.

```
for x = 1:2:20 # Steps of 2 - only odd values x
print(x, " ")
end
```

```
1 3 5 7 9 11 13 15 17 19
```

```
for x = 1:0.2:5 # Non-integers are ok
print(x, " ")
end
```

```
1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 2.6 2.8 3.0 3.2 3.4 3.6 3.8 4.0 4.2 4.4 4.6 4.8 5.0
```

```
for x = 10:-1:-5 # Use a negative step to count down
print(x, " ")
end
```

```
10 9 8 7 6 5 4 3 2 1 0 -1 -2 -3 -4 -5
```

```
for x = 10:2:5 # No repetitions since start > stop and step is positive
print(x, " ")
end
```

## 1.4.1. Example: The factorial function¶

```
function my_factorial(n)
y = 1
for i = 2:n
y *= i
end
y
end
```

```
my_factorial (generic function with 1 method)
```

```
my_factorial(5)
```

```
120
```

Note that the factorial function grows very fast with increasing inputs, and its value will quickly exceed the maximum value of the default `Int64`

type:

```
my_factorial(20) # This is OK
```

```
2432902008176640000
```

```
my_factorial(30) # Too large - overflow
```

```
-8764578968847253504
```

This particular value can still be computed by passing an `Int128`

to the function (which will automatically force all computations inside the function to use `Int128`

):

```
my_factorial(Int128(30))
```

```
265252859812191058636308480000000
```

However, this technique will eventually fail as well, and in general this is a good illustration that it is important to know what types are used internally (even if Julia is *weakly typed*, that is, it chooses the types for you) and what their limitations are.

Note that Julia has a built-in function for factorials, which gives an error if the return value is too large:

```
factorial(30)
```

```
OverflowError: 30 is too large to look up in the table; consider using `factorial(big(30))` instead
Stacktrace:
[1] factorial_lookup
@ ./combinatorics.jl:19 [inlined]
[2] factorial(n::Int64)
@ Base ./combinatorics.jl:27
[3] top-level scope
@ In[12]:1
[4] eval
@ ./boot.jl:360 [inlined]
[5] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1116
```

## 1.4.2. Example: Taylor polynomial¶

Consider the Taylor polynomial of degree \(2n\) for \(\cos x\):

A first version of a function to evaluate this could look like this:

```
function taylor_cos_bad(x,n)
y = 0
for k = 0:n
y += (-1)^k / factorial(2k) * x^2k
end
y
end
```

```
taylor_cos_bad (generic function with 1 method)
```

This function works as expected:

```
# x = 0.25, excellent approximation of degree 4
println(taylor_cos_bad(0.25, 2)) # Taylor approximation
println(cos(0.25)) # true value
```

```
0.9689127604166666
0.9689124217106447
```

```
# x = 10, very poor approximation of degree 4
println(taylor_cos_bad(10, 2)) # Taylor approximation
println(cos(10)) # true value
```

```
367.66666666666663
-0.8390715290764524
```

But note that if you try to increase \(n\), eventually you will run into the same problem as before with the `factorial`

function:

```
# x = 10, try approximation of degree 30
println(taylor_cos_bad(10, 15)) # Taylor approximation
println(cos(10)) # true value
```

```
OverflowError: 22 is too large to look up in the table; consider using `factorial(big(22))` instead
Stacktrace:
[1] factorial_lookup
@ ./combinatorics.jl:19 [inlined]
[2] factorial
@ ./combinatorics.jl:27 [inlined]
[3] taylor_cos_bad(x::Int64, n::Int64)
@ Main ./In[13]:4
[4] top-level scope
@ In[16]:2
[5] eval
@ ./boot.jl:360 [inlined]
[6] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1116
```

This problem is associated with something more fundamental, namely that the Taylor polynomial for large \(x\) and \(n\) divides very large numbers to produce small numbers. A better way to compute this is to note that both the factorial and the power of \(x\) can be implemented *incrementally*, that is, each term can easily be obtained from the previous one. This is true for the \((-1)^k\) factor as well, it simply says that the terms have alternating signs. These observations can be implemented in this better code:

```
function taylor_cos(x,n)
term = 1
y = 1
for k = 1:n
term *= -x^2 / ((2k-1) * 2k)
y += term
end
y
end
```

```
taylor_cos (generic function with 1 method)
```

This version easily computes with degree 30:

```
# x = 10, try approximation of degree 30
println(taylor_cos(10, 15)) # Taylor approximation
println(cos(10)) # true value
```

```
-0.839420205180993
-0.8390715290764524
```

and even higher:

```
# x = 10, try approximation of degree 100
println(taylor_cos(10, 50)) # Taylor approximation
println(cos(10)) # true value
```

```
-0.8390715290766048
-0.8390715290764524
```

## 1.4.3. Scope of Variables¶

The *scope* of a variable is the region of code within which a variable is visible. A new *local scope* is introduced by most code blocks. A local scope inherits all the variables from a parent local scope, both for reading and writing, unless the variable is specifically marked with the keyword `local`

. This is illustrated in the example below.

```
x = 10
y = 10
for i = 1:5
z = i # Local scope, only visible inside for-loop
x = z # Using x from parent scope
local y = z # Local scope, only visible inside for-loop (not using y from parent scope)
end
println(x) # = 5, since for loop modifies parent variable x
println(y) # = 10, since for loop uses local variable y
println(z) # Error: z only defined in the local scope of the for-loop
```

```
5
10
```

```
UndefVarError: z not defined
Stacktrace:
[1] top-level scope
@ In[20]:12
[2] eval
@ ./boot.jl:360 [inlined]
[3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1116
```

This can be convenient in for example *nested functions*, where the variables defined in the top function can be used in the inner function without having to pass it as a parameter.