Skip to the content of the web site.

Lesson 1.12: Local variables

Previous lesson Next lesson


You will remember that a function is supposed to solve a problem: calculate the absolute value of $x$, calculate the greatest common divisor of $m$ and $n$, etc. In some cases, when solving a problem, we will need to, or we could benefit from temporarily storing additional information.

For example, suppose you were asked to implement a function that evaluates a Taylor series of the functions $sin(x)$ and $cos(x)$ around the point $x = 0$. From your Calculus course, you know or will learn that a 10th-order Taylor series of both sine and cosine are

$sin(x) \approx x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \frac{x^9}{9!}$

where you will note that the first term is $\frac{x^1}{1!}$, and

$sin(x) \approx 1 - \frac{x^2}{2!} + \frac{x^4}{4!} - \frac{x^6}{6!} + \frac{x^8}{8!} - \frac{x^10}{10!}$

where again you will note that the first term is $\frac{x^0}{0!}$.

You are asked to implement these in C++, and while your first implementation is:

double sin_taylor( double x ) {
    return x - x*x*x/6.0            + x*x*x*x*x/120.0
             - x*x*x*x*x*x*x/5040.0 + x*x*x*x*x*x*x*x*x/362880.0;
}

double cos_taylor( double x ) {
    return 1 - x*x/2.0                       + x*x*x*x/24.0
             - x*x*x*x*x*x/720.0             + x*x*x*x*x*x*x*x/40320.0
	     - x*x*x*x*x*x*x*x*x*x/3628800.0;
}

but then you remember Horner's rule, and working hard, you realize that you can simplify this to

double sin_taylor( double x ) {
    return ((((x*x/72.0 - 1.0)*x*x/42.0 + 1.0)*x*x/20.0 - 1.0)*x*x/6.0 + 1.0)*x;
}

double cos_taylor( double x ) {
    return ((((-x*x/90.0 + 1.0)*x*x/56.0 - 1.0)*x*x/30.0 + 1.0)*x*x/12.0 - 1.0)*x*x/2.0 + 1.0;
}

You have reduced the number of multiplications from 45 to 17, so your new functions should be a lot faster; however, you have still calculated x*x nine different times. Wouldn't it be easier to calculate this once, and then to use that calculated value nine times?

For this, we can temporarily store a calculated value in the function and use that value inside the function. The calculated value must be associated with an identifier. That identifier is called a local variable because it is a value that is local to the function.

The way to give a new identifier a value and to then reuse that new value inside a function is as follows:

double sin_taylor( double x ) {
    double xx{x*x};
    return ((((xx/72.0 - 1.0)*xx/42.0 + 1.0)*xx/20.0 - 1.0)*xx/6.0 + 1.0)*x;
}

double cos_taylor( double x ) {
    double xx{x*x};
    return ((((-xx/90.0 + 1.0)*xx/56.0 - 1.0)*xx/30.0 + 1.0)*xx/12.0 - 1.0)*xx/2.0 + 1.0;
}

Just like we specify a parameter by giving the type and its identifier, we must specify the type and identifier of the local variable (in this case, double xx, but we must also give it an initial value, and that initial value appears between two braces:

	type_name identifier{ initial_value };

This is a declaration and initialization of a local variable.

In our function, once the local variable xx is declared and initialized, whenever the identifier xx appears, it will be replaced by the value that xx was initialized to. We have thus reduced the number of multiplications first from 45 down to 17 and now down to 10.

Let us look at a different example. Suppose we want to return the maximum root of a quadratic polynomial that has at least one real root. We will call this function

// Function declarations
double max_root( double a, double b, double c );

We will assume that this polynomial does have at least one real root.

// Function definitions
double max_root( double a, double b, double c ) {
    // Assert that it is not a straight line and
    // that the discriminant is not negative.
    assert( (a != 0) && ((b*b - 4.0*a*c) >= 0.0) );

    return max( (-b + std::sqrt( b*b - 4.0*a*c ))/(2.0*a),
                (-b - std::sqrt( b*b - 4.0*a*c ))/(2.0*a) );
}

You will notice that there are two inefficiencies:

  1. we are calculating b*b - 4.0*a*c three times, and
  2. we are calculating 2.0*a twice.

Again, it would be nice if we only had to perform these calculations once:

// Function definitions
double max_root( double a, double b, double c ) {
    double two_a{2.0*a};
    double disc{b*b - 4.0*a*c};

    // Assert that it is not a straight line and
    // that the discriminant is not negative.
    assert( (a != 0) && (disc >= 0.0) );

    return max( (-b + std::sqrt( disc ))/two_a,
                (-b - std::sqrt( disc ))/two_a );
}

You will note that we did not call the local variable 2a. Why?

Local variables can be used in many purposes, and their identifiers should help the reader to understand the significance:

    unsigned int count{0};
    double       PI{3.1415926535897932};
    bool         found{false};

The first local variable is counting something, and initially that count is zero, but later as we are counting whatever it is we are counting, we can increase that value. The second local variable is storing the value of $\pi$. The third is a Boolean flag indicating that something has not yet been found, but when what is being searched for, we can set that value to true.

Casting initial values

When you provide an initial value, the compiler will under some circumstances cast the initial value in the type. For example, if you have an integer value initializing a double, the compiler will convert that integer value to a floating-point value. If, however, you attempt to initialize an integer with a floating-point number, the compiler will object, even if that floating-point number is an integer value.

Default initial values

The default initial value of a local variable is 0 or false, as appropriate. You can specify that the local variable be initialized with an initial value by simply using a pair of braces:

    unsigned int count{};        // same as count{0};
    double       sum{};          // same as sum{0.0};
    bool         found{};        // same as found{false};

While this is reasonable to assume, it is, never-the-less easier to specify the initial value, if it is the default. Thus, as a matter of practice, the following is preferred:

    unsigned int count{0};
    double       sum{0.0};
    bool         found{false};

No initial values (uninitialized local variables)

It is possible to not give a local variable an initial value:

    unsigned int count;
    double       sum;
    bool         found;

These variables will be associated with memory somewhere, and if they are not initialized, their values will be based on what was in those memory locations when the function is called. Often, they may be 0 or false, but in general this is almost always a bad idea.

Consider the following program, which you can execute on repl.it.

// Pre-processor include directives
#include <iostream>

// Function declarations
int main();
void with_no_initial_values();

// Function definitions
int main() {
    with_no_initial_values();
    std::cout << "Pi = " << 3.14 << std::endl;
    with_no_initial_values();

    return 0;
}

// Print the values of three local variables,
// none of which are initialized
void with_no_initial_values() {
    int n;
    double x;
    bool b;

   std::cout << n << ", " << x << ", " << b << std::endl;
}

This calls the function with_no_initial_values(), which prints the values of these uninitialized local variables, it then prints a very poor approximation of $\pi$, and then calls the same function again. As you will note, just printing something to the screen causes at least some of the uninitialized local variables to start with different values.

What is most bizarre, however, is that also changes depending on what system you run this code on:

// Output on ecelinux.uwaterloo.ca
4196861, 9.88131e-324, 0
Pi = 3.14
6295680, 2.07323e-317, 0

// Output on churchill.uwaterloo.ca (Windows)
1627411690, 2.122e-314, 0
Pi = 3.14
1817066142, 5.26696e+213, 97

// Output on repl.it
0, 6.9375e-310, 0
Pi = 3.14
32623, 6.9307e-310, 0

// Three executions on eceubuntu.uwaterloo.ca
32614, 0, 3
Pi = 3.14
32766, 6.92068e-310, 112

32685, 0, 77
Pi = 3.14
32765, 6.93581e-310, 145

32619, 0, 38
Pi = 3.14
32764, 6.92177e-310, 111

Thus, there is not even a guarantee that the values of uninitialized local variables will be the same on successive executions of the exact same program.

Not initializing local variables
In general, do not allow a local variable to go uninitialized. If you are going to do so, you must comment why you are doing so, and that reason should be defensible.

Scope of local variables

We have discussed how a local variable is temporary storage for data that is restricted to the scope of a function. It is used to store data that is only needed for the function to accomplish its task.

The scope, however, is actually more restricted than that: The scope of a local variable is restricted from the point at which it is defined up until the end of the block in which it is defined.

Recall that a block of code is a collection of statements surrounded by braces. Up to this point, the only blocks of statements we have seen is the function body and the bodies of conditional statements.

It is an error to try to access a local variable either:

  1. before it is declared, or
  2. after the end of the block in which it was declared.

The time during which the local variable can be accesses is described as that local variable's lifetime.

Parameters as local variables

It is reasonable to consider parameters to be local variables of the function body, only the initialization is done through the arguments that are passed when the function is called.

Using an identifier more than once

In general, it is a bad idea to have two local variables in the same function have the same identifier. There are rules under which this is possible, and if you wish to look at the details, please read this article on Oddities with local variables. This document should satisfy the mind of the enthusiast, but it is not required reading for this course. In general, do not ever have two local variables in a function have the same name. There is one case where it may be reasonable:

  • when a local variable is required in two or more consequent or alternative bodies of a (possibly cascading) conditional statement, and
  • where that local variable serves approximately the same purpose in each of the blocks.

One final comment on scope

A local variable can only ever be accessed in the block of statements in the function in which it is declared. Once the variable goes out of scope, it can no longer be accessed. A local variable defined in one function cannot be accessed directly in another function (we will see pointers and references later). Thus,

  • If information stored in a local variable is required by the calling function, the value of that local variable should be returned.
  • If the information stored in a local variable is required by a function that is being called, then that local variable should be passed as an argument to the function being called.

Assigning to local variables

To this point, we have discussed the initialization of local variables, but we may also want to modify the value stored in a local variable. When we modify the value stored in a local variable, we will call that assigning to that local variable. This is done with the assignment operator, which happens to look like an equal sign:

// Pre-process include directives
#include <iostream>

// Function declarations
int main();

// Function definitions
int main() {
    int n{42};

    std::cout << n << std::endl;  // This prints '42'

    n = 91;      // The value of '42' is lost forever

    std::cout << n << std::endl;  // This prints '91'

    return 0;
}

This will print the two values 42 and then 91. Both of these refer to the same memory location, so the value 91 overwrites the value 42 during the assignment.

In general, an assignment will look like:

        variable_name = expression;

where in general the expression can be anything that evaluates to something of the same type as the type that variable_name was declared. (This may require upcasting.)

Reading an assignment statement
Always read the above assignment statement as The variable variable_name is assigned the value of the right-hand expression.

Thus, if you see:

	int n{42};
	int m{91};

	m = n + 1;

always read this as "'m' is assigned the value of 'n' plus one." This will save you a lot of grief in the future, for it is incorrect to say "'m' equals 'n' plus one." and missing out on this will cause you grief. On the other hand, if you see:

	int n{42};
	int m{91};

	if ( m == (n + 1) ) {
		// Some statements
	}

you should read this as "if 'm' equals 'n' plus one, then execute these statements."

Local variables can be assigned as long as they are in scope, and can be assigned to as often as you require. Think of a local variable as a scrap piece of paper where you can write down one and only one piece of information. If you want to enter something new on that piece of paper, you must erase what is there and then overwrite it.

Swapping two variables

Suppose we have two variables m and n, each assigned with a specific value. How can we swap those two values? For example, suppose we are attempting to author an algorithm that implements the greatest-common divisor of two variables m and n. For the algorithm to work, it is necessary to swap the two local variables if the first is not greater than the second:

    int gcd{};     // The local variable to which we will assign
                   // the greatest common divisor of 'm' and 'n'

    if ( m == n ) {
        // the gcd is m
        gcd = m;
    } else {
        if ( m < n ) {
            // Swap 'm' and 'n'
        }

        assert( m > n );
        // Calculate the gcd
    }

If you would like a more algorithmic example, a leftist heap is a data structure that allows for a fast priority queue that has, in addition very desirable characteristics that may be used in real-time operating systems. Internally, each datum accesses two other data, and it is a common operation to swap those two data to ensure the data structure remains as efficient as possible.

Unfortunately, it is very difficult to give practical examples of where such algorithms are absolutely necessary, as it would take an hour simply to set up a description of why such an example is relevant. Thus, we ask forgiveness for using examples that you may consider boring, like calculating the greatest common divisor, but even calculating the greatest common divisor is useful in the design of scheduling algorithms for mission-critical real-time systems.

Continuing with our question of swapping two variables, we cannot simply assign one variable to another, for this overwrites the value stored in the other local variable:

        if ( m < n ) {
            // Swap 'm' and 'n'
            m = n;
            // The original value assigned to 'm' is gone
        }

Thus, the easiest mechanism is to create a temporary local variable which will be initialized with the value of m:

        if ( m < n ) {
            // Swap 'm' and 'n'
	    int tmp{m};     // Assuming 'm' is of type 'int'
	    m = n;          // 'm' and 'n' now both store the original value of 'n'
        }

Now that we have assigned m the value stored by n, we can assign n the value stored to the temporary local variable:

        if ( m < n ) {
            // Swap 'm' and 'n'
            int tmp{m};     // Assuming 'm' is of type 'int'
            m = n;          // 'm' and 'n' now both store the original value of 'n'
	    n = tmp;        // 'm' is now assigned the original value of 'n'
        }

If you are interested in how to swap variables without using intermediate values, please consider reading this document on swapping two variables. It is not required reading, and some material goes beyond what we have currently covered in this course.

If you want to visualize this process, suppose you and your friend have two hand-held white boards, and on each white board are poems too long to memorize. You want to swap each other's poetry, but you want to keep your own white boards. Thus, you call over another person (a temporary local variable) who has a blank white board. You copy your poem onto that person's white board. You then erase your white board, and copy your friend's poem onto yours. Finally, you copy the poem on the other person's white board back onto your friend's white board. That other person is now no longer needed.

What is assignable

In your mathematics course, you could easily write $m + 1 = 10$, after which you may immediate deduce that $m = 9$. Unfortunately, if you think of the = operator as an equals sign and not as an assignment operator, you may be tempted to try:

        m + 1 = 5;

Should the compiler not be able to determine that m must therefore be assigned the value of 4? Unfortunately, this is not the case: the only items that can appear on the left-hand side of an assignment operator are local variables.

There are other expressions in C++ other than local variables that can be assigned, but for now, the only expression that can appear on the left-hand side of an assignment operator is a variable identifier.

The C++ programming language calls all expressions that can be assigned to lvalues (short for left-hand-side-of-assignment-operator-values). If the wrong item appears on the left-hand side of an assignment operator, you will get an error message that refers to lvalues. For example, if you were to try to compile this code:

// Function declarations
int main();

// Function definitions
int main() {
        int n{10};
        10 = 17;
        10 = n;
        n + 1 = 20;

        return 0;
}

you get the following error messages:

example.cpp: In function 'int main()':
example.cpp:7:5: error: lvalue required as left operand of assignment
  10 = 17;
     ^
example.cpp:8:5: error: lvalue required as left operand of assignment
  10 = n;
     ^
example.cpp:9:8: error: lvalue required as left operand of assignment
  n + 1 = 20;
        ^

What does assignment evaluate to?

This may seem like an odd question: an assignment operator should simply assign the result of the right-hand side to the variable on the left-hand side of the operator.

Consider, however, that all operators appear to evaluate to some sort of value. For example, + evaluates to the sum of the two operands, whereas || evaluates to the logical OR of the two operands (either true or false). Thus, what does an assignment statement evaluate to?

We can try this out as follows:

// Pre-processor include directives
#include <iostream>

// Function declarations
int main();

// Function definitions
int main() {
    int m{0};

    std::cout << m << std::endl;
    std::cout << (m = 42) << std::endl;
    std::cout << m << std::endl;
    std::cout << (m = 91) << std::endl;
    std::cout << m << std::endl;

    return 0;
}

You can run this on repl.it.

If you were to execute the above program, the output would be:

0
42
42
91
91

Thus, it seems that the assignment operator evaluates to the value that was assigned; however, it is closer to how std::cout << "Hi!" works; it returns a reference to the variable that was assigned. You can even use that in your code, so while we would never ever recommend you coding like this, the following is a completely valid statement:

    // Never do this in your code...
    //  - this would be pure evil, but it compiles
    std::cout << ((m = 616) + 50) << std::endl;
    std::cout <<   m              << std::endl;

The output is

666
616

This is because first m = 616 is executed, which assigns to m the value of 616. This evaluates to m (now assigned 616) which becomes the left-hand operand of +, so after this, this evaluates to 616 + 50, which then evaluates to 666, which is what is printed. The next line shows that never-the-less, m is still assigned the value of 616.

Thus, you may see the assignment operator as something different from other operators in C++, but really, it isn't. It is more correct to say that the assignment has a side-effect of actually assigning the right-hand side to the variable, and the entire expression evaluates to a reference to the variable that was assigned.

There is one situation where this may be convenient:

    // Suppose we want to set 'a', 'b' and 'c' all to 0
    a = 0;
    b = 0;
    c = 0;

You may, however, see the following at the work-place:

    // Suppose we want to set 'a', 'b' and 'c' all to 0
    a = b = c = 0;

You may ask yourself how this works. For most operators, a sequence of those operators are always evaluated left to right. For example, if you were to do the following: w + x + y + z, the compiler will always interpret this as ((w + x) + y) + z. Let's try these two out:

// Pre-processor include directives
#include <iostream>

// Function declarations
int main();

// Function definitions
int main() {
  int m{3};
  (m = 5) = 7;
  std::cout << m << std::endl;   // prints 7

  int a{1}, b{2}, c{3};

  a = (b = (c = 0));
  std::cout << a << ", " << b << ", " << c << std::endl;

  a = 1;
  b = 2;
  c = 3;

  ((a = b) = c) = 4;
  std::cout << a << ", " << b << ", " << c << std::endl;

  a = 1;
  b = 2;
  c = 3;

  a = b = c = 0;
  std::cout << a << ", " << b << ", " << c << std::endl;

  return 0;
}

You can execute this at repl.it.

The first assignment statement, (m = 5) = 7; first evaluates m = 5, after which m is assigned the value 5 and this evaluates to a reference to m. Thus, the next operation is m = 7, which assigns m the value of 7, and this is what is printed out in the next std::cout statement.

The second assignment statement a = (b = (c = 0)); first sees c assigned the value 0, and the assignement evaluates to a reference to c. Thus, the next expression to be evaluated is b = c, which assigns b the value of c, which is 0, and this evaluates to a reference to b. Finally, we see the operation a = b which assigns a the value assigned to b, which is now also 0, so finally we see that all three variables are assigned zero.

The third assignment statement ((a = b) = c) = 4 now first evaluates a = b, which assigns to a the value of b, which is 2. This evaluates to a reference to a, so the next operation is a = c, which assigns to a the value of c, which is 3, and this returns a reference to a. Finally, we see threfore a = 4, which assigns the variable a a value of 4 and we are finished. In this example, b and c are unchanged and a is ultimately assigned the value of 4.

The last assignment statement a = b = c = 0; should behave the same as a = (b = (c = 0));, and therefore we now observe that for the assignment operator without parentheses, operations are evaluated from right to left, which is the opposite direction that one would normally anticipate; however, it still makes sense.

Challenge

What is the output of the following program?

#include <iostream>

int main();

int main() {
    double a{3}, b{4}, c{5};
    std::cout << ((a = 3*(b = 2*(c = a + 2*b*c - 1) + 7) + 2*b - 8*c + 131) - 100)
              << std::endl;
    std::cout << a << ", " << b << ", " << c << std::endl;

    return 0;
}

You can execute this program on repl.it, but you first attempt to determine both the output and the subsequent values of a, b and c.

Just in case you're interested, you can reewite the above code six separate statements, which are much clearer than the single statement above.

    double a{3}, b{4}, c{5};
    c = a + 2*b*c – 1;
    b = 2*c + 7;
    a = 3*b + 2*b - 8*c + 131;
    std::cout << (a - 100) << std::endl;
    std::cout << a << ", " << b << ", " << c << std::endl;

Automatic assignment operators

A comment on the roots of words. The word "automatic" comes from the Greek word automatos, which means to "acting on itself". For example, an "automatic transmission" shifts gears by itself while an "automobile" has mobility by itself.

The C++ programming language has automatic assignment operators, where the assignment is to the variable itself.

AssignmentAutomatic
assignment
Name
a = a + 32a += 32auto-addition
b = b - 41b -= 41auto-subtraction
c = 2*cc *= 2auto-multiplication
d = d/10d /= 10auto-division
e = e % 100e %= 100auto-remainder

Thus, for example, the following pairs of instructions perform the exact same operation:

	a = a*(b + 3);
	a *= b + 3;

	b = b - 3 + a
	b += -3 + a

As with the assignment operator, these automatic assignment operators all return a reference to the variable being assigned to.

Questions and practice:

1. Consider the following two implementations of finding the maximum of four entries:

double max4a( double a, double b, double c, double d );
double max4b( double a, double b, double c, double d );

double max4a( double a, double b, double c, double d ) {
	// Assume 'a' is the largest
	//  - 'max_value' is declared a local variable of type 'double'
	//    and initialized with the value of 'a'
	double max_value{a};

	// If 'b', 'c' or 'd' are larger, update 'max_value'
	if ( max_value < b ) {
		max_value = b;
	}

	if ( max_value < c ) {
		max_value = c;
	}

	if ( max_value < d ) {
		max_value = d;
	}

	return max_value;
}

double max4b( double a, double b, double c, double d ) {
	if ( a >= b ) {
		if ( a >= c ) {
			if ( a >= d ) {
				return a;
			} else {
				return d;
			}
		} else {
			if ( c >= d ) {
				return c;
			} else {
				return d;
			}
		}
	} else {
		if ( b >= c ) {
			if ( c >= d ) {
				return c;
			} else {
				return d;
			}
		} else {
			if ( b >= d ) {
				return b;
			} else {
				return d;
			}
		}
	}
}

Are you sure that the first implementation is correct? Are you certain the second implementation is correct? In fact, the second implementation is not correct: find the error and correct it.

2. Even if you correctly fixed the mistake in the one implementation of max4, which implementation would be better from a programmer's point of view? For example, how much time did it take you to convince yourself that the implementation of max4a was correct? How much time would it take you to convince yourself that your correction to max4b is in fact correct?

3. In the function max4a, you could have used the local variable e instead of max_value; i.e.,

double max4a( double a, double b, double c, double d );

double max4a( double a, double b, double c, double d ) {
	double e{a};

	if ( e < b ) {
		e = b;
	}

	if ( e < c ) {
		e = c;
	}

	if ( e < d ) {
		e = d;
	}

	return e;
}

While this is easier to type, why is this a really bad idea?

4. The area $A$ of a triangle with sides of length $a$, $b$ and $c$ may be calculated using the Heron's formula where

$$A = \sqrt{ s(s - a)(s - b)(s - c)}$$

where

$$ s = \frac{a + b + c}{2}.$$

Write a function that takes three arguments and returns the area.

double triangle_area( double a, double b, double c );

double triangle_area( double a, double b, double c ) {
	// Enter your implementation here

	return 0.0;
}

5. How many floating-point additions and floating-point multiplications are performed in your implementation of Heron's rule? You should treat dividing by 2.0 as a multiplication by 0.5. (This is assuming you are using a local variable s.)

Suppose someone chose to not use a local variable s and instead just implemented the function as

double triangle_area( double a, double b, double c );

double triangle_area( double a, double b, double c ) {
	return std::sqrt( (a + b + c)/2.0*((a + b + c)/2.0 - a)
        	*((a + b + c)/2.0 - b)*((a + b + c)/2.0 - c) );
}

What is the total number of floating-point operations now?

6. You will recall from secondary school the idea of a Fibonacci numbers $F(n)$, where $F(0) = F(1) = 1$ and where $F(n) = F(n - 1) + F(n - 2)$ for $n \ge 2$. Write a function that calculates the $n$th Fibonacci number using the formula

$$\frac{\phi^n - (-\phi)^{-n}}{\sqrt{5}}$$

where $\phi = \frac{\sqrt{5} + 1}{2}$. In your first year courses, you will see how the Fibonacci numbers that normally must be explicitly calculated ($F(2) = 2$, $F(3) = 3$, $F(4) = 5$, ...) can be solved using this closed-form formula. You will have to use the double std::pow( double, double ) function to calculate powers of $\phi$.

7. In your last function, you had to write a function that returned $F(n)$. Should the signature of your function be

double F( int n );

? Why might this be a poor idea from a programmer's point of view? What would be a better name? Remember, you will almost never be the sole user of the functions you write.

8. In Question 6, you wrote a function that calculated $F(n)$. Because floating-point arithmetic is not precise, your answers that you get will sometimes be not an exact integer. For example, you may calculate $F(25) = 121392.99999999$ instead of $121393$. The Fibonacci numbers, however, should be integers. Thus, instead of returning the number you calculated, you should return:

	// Your code...
	return std::round( your_result );

which will round the number to the closest integer.

9. What happens if you try to calculate $F(1470)$ or $F(1480)$? What is the largest value of $n$ for which you can calculate the corresponding Fibonacci number? Should you document this in your comments?

10. What happens if you try to calculate $F(-1)$ or $F(-2)$ or $F(-3)$. Do these results make sense from the definition of the Fibonacci numbers?


Previous lesson Next lesson