Basic Data Types and Operators
3.8 Global and local variables
The functions that we have written so far interact by passing parameters and returning values.
Sometimes you may think it is a good idea to share a variable between functions.
As an example, there is no standard definition for the number π in C++.
So you might want to write the following code.
c o n s t␣d o u b l e␣PI␣=␣3 . 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 ; d o u b l e␣c o m p u t e A r e a ( int␣r )␣{
d o u b l e␣a n s w e r␣=␣0.5␣*␣PI␣*␣r␣*␣r ; r e t u r n␣a n s w e r ;
}
d o u b l e␣c o m p u t e C i r c u m f e r e n c e ( int␣r )␣{
d o u b l e␣a n s w e r␣=␣2.0␣*␣PI␣*␣r ; r e t u r n␣a n s w e r ;
}
The first defines a double variable called PI with the given numeric value.
It uses the const keyword to indicate that the value is not allowed to change.
Because PI is declared outside of any function it is called a global variable.
By contrast, the variable r in each function is called a local variable.
The names you use for local variables within a function have no relationship with the names you use in another function. For example, we have reused the variable name “answer” in two different functions to refer to different quantities. Because these variables are local variables they don’t interfere with each other.
The scope of a variable refers to the parts of code where that variable can be used. So we say that PI has global scope and r has local scope. In C++, the curly brackets determine the scope of a variable. If a variable is first mentioned within a set of curly brackets, it can only be referred to within those brackets.
Once the execution of the code leaves those brackets, the variable is deleted.
We will discuss scope further in Section 4.7.
Tip: Avoid global variables other than constants
Over time, computer programmers have learned that using global variables makes code hard to understand. When writing programs you should try to divide things into small independent pieces. Using global variables prevents
these pieces being truly independent. When you change a global variable that is used by some other bit of code you are unaware of, you may accidentally break that code.
Danger!
In Chapter 20 we will learn how to write code which can execute more than one function simultaneously. This is called multi-threaded code. If you use global variables in multi-threaded code you can have a situation where two bits of code are trying to change the same variables at the same time. This is called a race condition. To prevent this happening, you need to use a technique called locking. See Chapter 20 for details on how to do this.
3.9 Namespaces
We wrote a function called average in the previous section. In some ways this isn’t a very good idea because the word average is ambiguous. Do we mean the mean, mode, or median?
This might not seem a big problem. Now we’ve noticed the ambiguity, we could just rename the function.
However, suppose we are using two libraries written by other teams (or perhaps completely different companies). In one library they have used the word average to indicate the mean. In another library they use the word average to indicate the median. These functions are not written by us, so we can’t change them. Will this mean the libraries are incompatible?
Furthermore, changing the name of a function isn’t always as easy as it sounds. If we have written a library that somebody else is using, we can’t change the name of a function without also changing their code. For this reason, you can’t normally rename the functions in a library once you have released the library to your customers.
The same problem will occur if two libraries use identically named global variables.
To get round these problems, C++ has a mechanism called namespaces.
All global variables and functions have an associated namespace which can be used to identify the function more precisely when necessary. Unless you specify the namespace for your functions, they will be put in the global namespace.
In a similar way, English people have a first name that you can use to refer to them and a second name that you can use to help resolve ambiguities.
As an example, the global variables cin and cout that we have been using extensively are actually declared in a namespace called std. If you were to create your own variables with the same names, you could still refer to the familiar variables by using the qualified names std::cin and std::cout.
In fact, unless you explicitly declare that you are using a namespace with the using␣namespace command, you will have to fully qualify the names.
This is why all our programs have begun with the line using␣namespace␣std;
The line above means that we want to use any code in the C++ standard library without the need to qualify the names. One can get rid of this line, but at the expense of needing to fully qualify the variable name as shown below:
#include␣<iostream>
int␣main()␣{
␣␣␣␣std::cout␣<<␣"Hello␣World\n";
}
You can put your own functions into a namespace as follows.
n a m e s p a c e␣g e o m e t r y␣{
d o u b l e␣c o m p u t e A r e a ( int␣r )␣{
d o u b l e␣a n s w e r␣=␣0.5␣*␣PI␣*␣r␣*␣r ; r e t u r n␣a n s w e r ;
}
d o u b l e␣c o m p u t e C i r c u m f e r e n c e ( int␣r )␣{ d o u b l e␣a n s w e r␣=␣2.0␣*␣PI␣*␣r ; r e t u r n␣a n s w e r ;
} }
This code creates two functions and puts them both in a namespace called geometry.
We won’t write our own namespaces in this book simply because doing so would make our code examples a little longer. However, in real code that you expect to use in practice and share with other people, you should always use namespaces to avoid potential naming conflicts.
Exercises
In the questions below write lots of functions (at least one per question) but only one main function that should run each of your functions in turn to check that they work. Notice that the questions are only interested in the functions that you write and not in the main method that tests them. So there’s no need to write an interactive program, just check that your functions work for a few input values.
3.9.1. Write a recursive function to compute the sum of the numbers between 1 and n.
3.9.2. Write a recursive function that takes two integer parameters a and b and prints out all the numbers from a to b.
3.9.3. The n-th Fibonacci number can be defined by xn = xn−1+ xn−2 if n ≥ 2. We define x0 = 1 and x1 = 1. Write a function fibonacci that evaluates the n-th Fibonacci number by recursion. How many times is the function fibonacci called in order to compute each of x2, x3, x4, and in general, xn? Don’t worry, we will find a far more efficient way to compute the Fibonacci numbers in the next chapter.
3.9.4. A commonly occurring function in financial mathematics is the cumu-lative normal function defined by:
normcdf(x) = N (x) = 1
√2π Z x
−∞
exp(−t2/2) dt If x >= 0 we define:
k = 1/(1 + 0.2316419x) a good approximation forN (x) is given by:
1 − 1
√2πexp(−x2/2)k(0.319381530 + k(−0.356563782 + k(1.781477937 + k(−1.821255978 + 1.330274429k)))).
For x <= 0 you can use the same formula to evaluate 1 − N (−x).
The formula can be derived by choosing the general functional form and then finding the coefficients that give the best fit. For this question, you should just accept the formula on face value.
Write a function called normcdf to evaluate the cumulative normal function.
Why would N be a bad name for the function?
3.9.5. Is √
2π recomputed every time your normcdf function is used? Use a global variable to improve this.
3.9.6. For each n ∈ N define a mathematical function hn as follows h0(x, a0) = a0
hn(x, a0, a1, a2, . . . , an) = a0+ xhn−1(x, , a1, a2, . . . , an).
We will call these “Horner functions” because they use the Horner method of evaluating a polynomial. Any polynomial in x can be rewritten as
hn(x, a0, a1, a2, . . . an)
for appropriate constants ai. The advantage of using h to evaluate the poly-nomial is that you don’t have to compute high powers of x.
Implement the first few Horner functions in C++. Give all your functions the same overloaded name hornerFunction. Use a Horner function to simplify your normcdf function.
Note: This question requires some tedious cutting and pasting. Hopefully you will want to rebel against this and find a better way of solving the problem than just cutting and pasting! However, in this case some tedious cutting and pasting is the right thing to do. This is a rare case where you should violate the Once and Only Once principle. The original code for normcdf should have been very fast because it contains no complex code like loops (to be covered in Chapter 4). We’re introducing hornerFunction to improve readability, but we don’t want to harm the speed too much. That’s why we don’t want to be too clever.
3.9.7. Implement the Moro algorithm for the inverse function of the cumula-tive normal distribution. Call the resulting function norminv.
The Moro algorithm proceeds as follows:
Suppose x ∈ [0, 1]. Define y = x − 0.5. If |y| < 0.42, define r = y2 and approximate norminv with the following formula:
y h3(r, a0, a1, a2, a3) h4(r, 1.0, b1, b2, b3, b4). We will define the constants ai and bi shortly.
Suppose |y| >= 0.42. If y is negative let r = x. Otherwise let r = 1 − x. Define s = log(− log(r)). Define t by
t = h8(s, c0, c1, . . . , c8).
If x > 0.5, norminv is approximated by t, otherwise by −t.
Here is a table of values for the constants:
a0␣=␣2.50662823884;
a1␣=␣-18.61500062529;
a2␣=␣41.39119773534;
a3␣=␣-25.44106049637;
b1␣=␣-8.47351093090;
b2␣=␣23.08336743743;
b3␣=␣-21.06224101826;
b4␣=␣3.13082909833;
c0␣=␣0.3374754822726147;
c1␣=␣0.9761690190917186;
c2␣=␣0.1607979714918209;
c3␣=␣0.0276438810333863;
c4␣=␣0.0038405729373609;
c5␣=␣0.0003951896511919;
c6␣=␣0.0000321767881768;
c7␣=␣0.0000002888167364;
c8␣=␣0.0000003960315187;
3.9.8. Write a function blackScholesCallPrice, which takes five param-eters (the strike price, time to maturity, spot price, volatility, and risk-free interest rate) and computes the call option price using the Black–Scholes for-mula. The formula is given in Equation (A.6) in the appendix.
3.9.9. Do you need to know about Moro’s algorithm or Horner’s method in order to use the norminv function? Explain the connection between this and the problems of scalability and maintainability.
3.9.10. How have you tested your code?
3.9.11. Use the online documentation for C++ to find out about the function erfc. What do you need to include to use this function? How can you use this function to simplify some of the code we have written in earlier exercises?
3.10 Summary
We have seen how the use of functions allows us to break our code into small pieces which can be reused.
In the exercises, we have written a function, blackScholesCallPrice, which relies on the function normcdf, which in turn depends upon the function hornerFunction. We can build highly sophisticated programs by combining functions. By getting different programmers to write different functions, we can divide knowledge across a team.
We have seen how C++ allows you to write recursive functions.
We have seen that every C++ function has a namespace, a name, and a signature. We have seen how C++ uses namespaces and types to resolve naming conflicts.
We have learned the difference between local and global variables. We have learned how to define const variables.