Learning Modern C++ For Finance (Early Release) - Daniel Hanson - 2022 - O'Reilly Media, Inc - 1098100794 - Anna's Archive
Learning Modern C++ For Finance (Early Release) - Daniel Hanson - 2022 - O'Reilly Media, Inc - 1098100794 - Anna's Archive
Finance
Foundations for Quantitative Programming
With Early Release ebooks, you get books in their earliest form—the
author’s raw and unedited content as they write—so you can take
advantage of these technologies long before the official release of these
titles.
Daniel Hanson
Learning Modern C++ for Finance
by Daniel Hanson
Copyright © 2022 Daniel Hanson. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc. , 1005 Gravenstein Highway North,
Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales
promotional use. Online editions are also available for most titles (
http://oreilly.com ). For more information, contact our
corporate/institutional sales department: 800-998-9938 or
corporate@oreilly.com .
Before launching into programming in C++, it will be useful to present a brief overview of the
language the C++ Standard Library, and the ways in which C++ continues to have a major
presence in quantitative finance.
You may have already felt intimidated by opinions and rumors claiming that C++ is
extraordinarily difficult to learn and fraught with minefields. So, in this chapter, we will try to
allay these fears by first debunking some of the common myths about C++, and then presenting
straightforward examples to help you get up and running.
Most of the content here is likely familiar for most readers, but the discussion here attempts to
extend some of the basics with points about quantitative programming and best practices that
often are not included in introductory books. We will also have our first look at C++20, namely
mathematical constants that have been added to the C++ Standard Library.
By the end of the chapter, you should be able to write, compile, and run simple C++ programs,
understand basic numerical types, and employ mathematical functions in the Standard Library
that are fundamental in just about any quantitative discipline, including finance.
In addition, C++ is not limited to object-oriented programming; rather, the language also
supports the other three major programming paradigms, namely procedural programming,
generic programming, and functional programming. Each of these will be discussed in
subsequent chapters.
C++ is a strongly-typed language, meaning that before we use a variable, we must declare it by
its type. The language provides a variety of numerical types; however, those that we will
primarily use are as follows:
Others, such as unsigned and extended integer types, will be introduced later when we need
them.
Use of Standard Library components, however, requires the programmer to explicitly import
them into the code, as they reside in a separate library rather than within the core language. The
idea is similar to importing a NumPy array into a Python program or loading an external
package of functions into an R script. In C++, this is a two-step process, starting with loading
the file containing the Standard Library declarations of functions and classes we wish to use,
and then scoping these functions with the Standard Library namespace name, `std` (often
pronounced as “stood” by C++ developers).
There are also several integrated development environments (IDE’s) available, namely Visual
Studio, Apple’s Xcode (which ships with the Clang compiler), and CLion, a product that
typically requires purchase from JetBrains. For this book, Microsoft’s Visual Studio compiler
and IDE are highly recommended. They are user-friendly options to get up and running quickly
on C++, with very powerful debugging tools.
Furthermore, the Visual Studio option also includes a Clang option that allows a programmer to
switch between it and the Microsoft compiler, helping to ensure cross-platform compatibility.
Unfortunately, the Visual Studio option for C++ only exists for Windows, as the Mac version
does not ship with a C++ option. In this case, one might opt for downloading Apple’s Xcode,
which ships with the Clang compiler. Linux users will typically want to opt for the gcc or Clang
compiler.
#include <iostream>
#include <string>
int main()
{
std::cout << "Hello World!" << '\n';
std::string person;
std::cout << "To whom do you wish to say hello? ";
std::cin >> person;
std::cout << "Hello "<< person << "!" << '\n';
return 0;
}
If you want to say hello to your mother, then after compiling and running the code, the screen
would resemble the following:
Hello World!
To whom do you wish to say hello? Mom
Hello Mom!
cout and cin, along with the string class, depend upon including the C++ Standard
Library declaration files iostream and string.
Members of the Standard Library need to be scoped by their namespace std. An
alternative is to put using statements with the namespace scopes at the top of the file,
indicating that anytime these elements appear in the code, they are understood to be
coming from the std namespace. Also, you may find it easier to type endl (end of line)
rather than '\n’:
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <string>
using std::string;
int main()
{
cout << "Hello World!" << endl;
string person;
cout << "To whom do you wish to say hello? ";
cin >> person;
cout << "Hello " << person << "!" << endl;
return 0;
}
is sometimes used to replace the individual using statements; however, this is not
considered good practice, as it can result in naming clashes at compile time. The
motivation behind namespaces will be presented in Chapter 3.
Output to and input from the console is almost never used in production-level financial
programming. User input data will typically come from graphical user interfaces (GUIs)
or web applications, while market data usually comes from live feeds. Results are
typically displayed in the user interface and then stored in a database, such as when a trade
in executed.
We will use cout and cin to sometimes mimic these inputs, but they should be avoided
in production code.
NOTE
For larger and more robust production applications, we will soon look at writing functions in separate modules,
using a new feature in C++20, in which the same method of declaring and implementing functions will carry over.
Further details on functions follow in the next two subsections.
Function declarations
C++ functions may or may not return a value; furthermore, they may or may not take input
arguments. A function that has no return value is indicated by a `void` return type. For
example, if we move our “Hello World” example into a separate function, it would simply
output a message to the screen without returning a value when called from the `main` function,
so it would be declared as a `void` function. In addition, it does not require any input
parameters, so its declaration would take on the form
void hello_world();
Next, suppose we want to write a real-valued function that takes in a single variable and returns
twice its value. In this case, our declaration will have a double precision floating type return,
indicated by `double`, and an input of the same type. If we name this function `twice_a_real`,
and the input variable `x`, our declaration would be written as
As a final example, as in other programming languages, a function can take in more than one
variable. Suppose we wish to add three integers in a function called `add_three_ints` and return
the sum of variables `i`, `j`, and `k`. Integer types are indicated by `int`, so our function
declaration would be
Function implementations
Function implementations, also called function definitions, are where we implement the actual
commands to display a message to the screen, calculate a mathematical result, or to perform
other tasks. The body of the function is placed inside braces, as shown here for the
`hello_world` function. We again need to indicate the `void` return type.
void hello_world()
{
std::cout << "Hello World!\n";
}
Next, we can write the implementations of our two simple mathematical functions. As in their
declarations, the `double` and `int` return types, respectively, as well as the types of their input
variables, must be included:
double twice_a_real(double x)
{
double y = 2.0 * x;
return y;
}
int add_three_ints(int i, int j, int k)
{
return i + j + k;
}
In the first case, we initialize a new `double` variable `y` and with the result of the calculation.
Because C++ is a strongly typed language, we need to indicate the type of a variable when it is
initialized. This variable is then returned to the `main` function with the result. In the second
function, we just put the sum operations in the return statement itself; this is also perfectly
legal.
Finally, we put this all together with a `main` function that is called when the program starts
and makes calls to our user-defined functions. It goes in between the user-defined function
declarations and their implementations below, as shown here:
#include <iostream>
// Maybe put in using statements here(?)
void hello_world();
double twice_a_real(double x);
int add_three_ints(int i, int j, int k);
int main()
{
hello_world();
double prod = twice_a_real(2.5);
std::cout << "2 x 2.5 = " << prod << std::endl;
std::cout << "1 + 2 + 3 = " << add_three_ints(1, 2, 3) << std::endl;
double r;
std::cout << "Enter a real number: ";
std::cin >> r;
std::cout << "2 x " << r << " = " << twice_a_real(r) << std::endl;
return 0;
}
void hello_world()
{
std::cout << "Hello World!\n";
}
double twice_a_real(double x)
{
double y = 2.0 * x;
return y;
}
int add_three_ints(int i, int j, int k)
{
return i + j + k;
}
Syntax Review
Commands and declarations in C++ terminate with a semicolon:
double y = 2.0 * x;
double x1 = 10.6;
int k; // Defaults to zero
double y1 = twice_a_real(x1);
NOTE
C++11 introduced the `auto` keyword that can automatically deduce a variable or object type, as well as uniform
initialization (with braces). Varied opinions on their use exist, but many programmers still prefer to explicitly state
plain old data (POD) types such as `int` and `double` to avoid ambiguity. This will be the style followed in this
book. `auto` and uniform initialization will be discussed later within contexts where they tend to be more useful.
// This is a comment
/*
Owl loved to rest quietly whilst no one was talking
Sitting on a fence one day, he was surprised when
suddenly a kangaroo ran close by.
*/
There is no difference to the compiler between a single space or multiple spaces; for example,
despite the variations in whitespace, the following code is legal:
int j = 1101;
int k= 603;
int sum = j + k;
std::cout << "j + k = " << sum << "\n";
int j = 1101;
int k = 603;
int sum = j + k;
std::cout << "j + k = " << sum << "\n";
Again, for more realistic and complex code, this mantra should be kept in mind. It will be a
recurring theme throughout this book.
Code may also be continued onto multiple lines without the use of a continuation character, and
vertical spaces are ignored. Returning to our previous example, writing
int j = 1101;
int k =
603;
int sum = j + k;
std::cout << "j + k = "
<< sum
<< "\n";
would yield the same result. As before, the preceding example, with uniform spacing and each
command placed in a single line, would be preferable. However, it should be noted that, in
quantitative programming where complex and nested calculations are involved, it often
becomes highly advisable to split up formulae and algorithms on multiple lines for clarity and
code maintainability. We will see examples of this in subsequent chapters.
Finally, C++ syntax is case sensitive. For example, two `double` variables `x` and `X` would be
as different as two other variables `kirk` and `spock`. The same applies to function names. In
examples above, we used the Standard Library function `std::cout`. Attempting to write
`std::Cout` instead would trigger a compiler error.
Naming Conventions
Variable, function, and class names can be any contiguous combination of letters and numbers,
subject to the following conditions:
Names must begin with a letter or an underscore; leading numerals are not allowed.
Other than the underscore character, special characters, such as `@`, `=`, `$` etc are not
allowed.
Spaces are not allowed. Names must be contiguous.
Language keywords are not allowed in naming, such as `double`, `if`, `while`, etc. A
complete listing can be found on https://en.cppreference.com/w/cpp/keyword.
The maximum name length is compiler-dependent, and in at least one case – the GNU gcc
compiler – imposes no limitation; however, see the mantra discussed above.
Single letter variable and function names are fine for simple examples and plain mathematical
functions. However, for quantitative models, it will usually be better to pass function arguments
with more descriptive names. Function and class names as well should also provide some
indication of what they do.
Several naming styles have been common over the years, namely
Lower Camel case; eg, `optionDelta`, `riskFreeRate`, `efficientFrontier`: Letter of first
word in lower case, and following words capitalized
Upper Camel, aka Pascal case; eg, `OptionDelta`, `RiskFreeRate`, `EfficientFrontier`:
Letter of each word is in upper case
Snake case; eg, `option_delta`, `risk_free_rate`, `efficient_frontier`: Each word begins
with lower case, separated by an underscore character
Lower Camel and Snake cases are the most typical of what is found in C++ function and
variable names, and class names are usually in Upper Camel form. In recent years – likely
propelled by Google’s C++ Style Guide [5] – variable and function names have gravitated more
toward the snake case. As such, we will adopt this convention in this book, and use Upper
Camel for class names.
In cases where single characters are used for integral counting variables, it is still common to
use the FORTRAN convention of letters `i` through `n`, although this is not required. We will
also adopt this practice.
// integers:
int i = 8;
int j = 5;
int k = i + 7;
int v = j - 3;
int u = i % j;
// double precision:
double x1 = 30.6;
double x2 = 8.74;
double y = x1 + x2;
double z = x1 - x2;
double twice_x2 = 2.0 * x2;
The order and precedence of arithmetic operators are the same as found in most other
programming languages, namely:
i + j - v
x1 + twice_x2/x2
Using the above double precision values would result in 30.6 + 2.0 = 32.6
(x1 + twice_x2)/x2
`cos(x)` cosine of x
`sin(x)` sine of x
`tan` tangent of x
`exp` exponential function ex
`log` natural logarithm ln(x)
`sqrt` square root of x
`cbrt` cube root of x
`pow` x raised to the power of y
`hypot`
As these are contained in the Standard Library rather than as language features. The `cmath`
header file should always be included, with the functions scoped by the `std::` prefix:
Again, if you don’t feel like typing out `std::` all the time, putting `using` statements after the
`include` statement are also fine:
We can also now write our first finance example. We want to price a zero coupon bond
Ae-rt
where
A = the face value of the bond,
r is the interest rate, and
t is the time to maturity as a year fraction.
In C++, we could then write
For a more comprehensive list of Standard Library math functions, again see Josuttis, The C++
Standard Library (2E) [4], Section 17.3, or the listing available on the CppReference website
[6]. Both are indispensable references for any modern C++ developer and are highly
recommended advanced complementary resources for this book. Some additional guidance on
the use of Standard Library math functions follows in the next two sections.
double f(double x)
{
return x * (x * (x * (8.0 * x + 7.0) + 4.0 * x) - 10.0) - 6.0;
}
rather than
double f(double x)
{
return 8.0 * std::pow(x, 4) + 7.0 * std::pow(x, 3) +
4.0 * std::pow(x, 2) + 10.0 * x - 6.0;
}
NOTE
Note: Regarding C headers and namespace std, this is clarified, for example, in the specifications for the gcc
compiler:
The standard specifies that if one includes the C-style header (<math.h> in this case), the symbols will be
available in the global namespace and perhaps in namespace std:: (but this is no longer a firm requirement.) On
the other hand, including the C++-style header (<cmath>) guarantees that the entities will be found in
namespace std and perhaps in the global namespace.[8]
Constants
In any type of quantitative programming, there is often a need to use constant values in
calculations. In C++, one can define a constant by simply appending the keyword `const` when
a value is assigned. Furthermore, beginning with C++20, a set of commonly used mathematical
constants is now available.
Then, if later within the same scope someone attempted to reassign it to a different value:
a compiler error would result, with a message indicating an attempt was made to modify the
value of a constant. Catching errors at compile time is better than chasing them at runtime and
tracking down the cause, especially in a live production environment.
`const` also has other important uses and interesting properties that we will cover later,
particularly in an object-oriented programming context.
Definition e
To use these constants, one must first include the `numbers` header in the Standard Library. At
the time of this writing, each must be scoped with the `std::numbers` namespace. For example,
to implement the function
we could write
#include <cmath>
#include <numbers>
. . .
double some_fcn(double x, double y)
{
double math_inv_sqrt_two_pi =
std::numbers::inv_sqrtpi / std::numbers::sqrt2;
return math_inv_sqrt_two_pi*(std::sin(std::numbers::pi * x) +
std::cos(std::numbers::inv_pi*y));
}
This way, whenever is used in calculations for example, its value will be consistent
throughout the program, rather than leaving it up to different programmers on a project who
might use approximations out to varying precisions, resulting in possible consistencies in
numerical results.
std::sqrt(2.0)
std::numbers::sqrt2
holds the double precision approximation itself. While perhaps of trivial consequence in terms
of one-off performance, repeated calls to the `std::sqrt` function millions of times in
computationally intensive code could potentially have some effect.
NOTE
While not essential to know at this point, it is worth at least mentioning that these constants are set at compile time
rather than runtime, using a C++11 designation called `constexpr`. This ties in with the much broader and more
advanced subject of template metaprogramming, in which calculations of constant values to be used at runtime are
performed at compile time. [[Might return to this topic later, although it is of limited used in financial modeling
where the computations depend on data only available at runtime]].
As a closing note, it is somewhat curious that the set of mathematical constants provided in
C++20 include the value , but not or , despite the latter two being more
commonly present in statistical calculations. [[See later chapter on the Boost libraries – they are
included there]].
Conclusion
This concludes our whirlwind overview of C++. We emphasized quantitative programming,
along with the mathematical constants now included in C++20.
Our coverage of best practices with respect to coding style will be a consistent theme
throughout the book, as C++ is an extremely feature-rich language with plenty of the proverbial
rope with which to hang oneself. Adhering to best practices and consistent coding style is vital
to ensure code maintainability and reliability.
One other point to remember is that while we use a lot of screen output and input, this is not
how C++ is typically used in quantitative development. `std::cout`, and `std::cin` should be
thought as placeholders for real-world interfaces. We will continue to use them as devices to
check our results, but they will mostly be relegated to use within the test functions that are
called from `main()`, rather than within mathematical and models code itself where they should
be avoided in practice anyway.
References
[1] Kalb and Azman, C++ Today: The Beast is Back, available on
https://resources.jetbrains.com/storage/products/cpp/books/Cplusplus_Today.pdf (link)
[2] Guideline Support Library (ISO) (link)
[3] ISO C++ Coding Standards (link)
[4] Nicolai Josuttis, The C++ Standard Library (2E) (link)
[5] Google C++ Style Guide (https://google.github.io/styleguide/cppguide.html)
[6] cppreference.com
[7] Stepanov, Mathematics of Generic Programming (Horner’s Method)
[8] GNU gcc Compiler Documentation
(https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_headers.html
Chapter 2. Some Mechanics of
C++
Just about any programming language will have some form of an array
structure for storing collections of like types. In C++, there are a handful of
options for this purpose, but it is the Standard Library `vector` container
that is by far the most-used type. In this chapter, we will see how a `vector`
can conveniently represent a vector of real numbers in the mathematical
sense. We will also go through the basics of creating and using a `vector`
and its key member functions, as it will tie in well with iterative statements,
such as counted loops and `while` statements, also to be covered in this
chapter.
Control structures, include both iterative statements and conditional.
Conditional branching in C++ can be implemented in `if` statements,
similar to other languages, as well as in what are called `switch` statements.
A good complementary topic related to the `switch` statement is that of
enumerated types (enums), particularly the more modern enum classes that
were added in C++11. Enum classes are also well-suited for facilitating data
input to and output from financial models.
Finally, we will wrap up with a summary of aliases that can be used in C++,
and why they are important. This includes type aliases that can add clarity
to the code, in place of longer and sometimes more cryptic templated types.
References and pointers allow you to access and modify objects without the
overhead of object copy, although pointers have wider-ranging uses as well.
NOTE
Historical Note: A `vector` is more specifically part of what is called the Standard
Template Library (STL). The STL was developed independently of Bjarne Stroupstrup’s
early efforts in the 1980’s and 90’s to design and produce C++, by researcher Alexander
Stepanov. The history behind acceptance the STL and its acceptance into the C++
Standard is a very interesting one [[see Kalb/Azman]], but the upshot is the STL – based
on generic programming – was accepted into the theretofore object-oriented focused
C++ for its first ISO standard release in 1998.
#include <vector>
using std::vector;
//. . .
vector <double> x; // Vector of real numbers
vector <BondTrade> bond_trades; // Vector of user-defined BondTrade
objects
NOTE
The angle brackets indicate the template parameter. Templates are the means by which
C++ implements generic programming. This topic will be discussed in further detail in
Chapter 7.
The `vector` can be populated element by element as shown here. Note that
indexing starts with zero rather than one.
The index is indicated by square brackets. We can also change the values by
simply reassigning an element to a new value; viz,
v[1] = 13.68;
NOTE
The addition of uniform initialization to C++11 has had a significant impact on the
language, beyond simply initializing a vector. It has some interesting and convenient
properties that will be discussed in Chapter 4.
Member Functions
As `vector` is a class, it holds a number of public member functions,
including three: `at`, `size`, and `push_back`.
The `at` Function
The `at` function essentially performs the same roles as the square bracket
operator, namely access of an element for a given index, or to modify the
element.
The difference between using the square bracket operator and the `at`
function is the latter performs bound checking. Two examples are
Attempting to access an element that exceeds the maximum index; eg,
w.at(-3) = 19.28;
In each case, an exception will be thrown that can be used in error handling.
Otherwise, you can just think of `at` and `[.]` as the same.
You may notice this is the first time we have used the `auto` keyword. What
this does is automatically deduce the type returned from the `size` function.
We will see in future cases how useful `auto` can be, but here, it helps us
get around the fact that the maximum size of a `std::vector` container will
vary depending upon compiler settings and the platform you are using. The
type will be some form of an unsigned (non-negative) integer, of which
there are multiple sizes.
So as to not get into the weeds here, we don’t need to be concerned with the
specific unsigned type here, so we can mostly just use `auto` for the return
type of the `size` member function.
The `push_back` Function
This function will append elements to a `vector`; that is, new elements are
“pushed onto the back” of the container.
For example, we can append a new element to the `vector v` above, say
47.44:
v.push_back(47.44);
Now, `v` contains four values: 10.6, 58.63, 0.84, 47.44, with `v[3]` (fourth
element, using 0-indexing) equal to the new value.
We can also append values to an empty vector. At the outset, we defined
x.push_back(3.08); // x.size() = 1
`x` now contains the value 3.08 in its index 0 position and contains one
element. This can be repeated arbitrarily many times:
x.push_back(5.12); // x.size() = 2
x.push_back(7.32); // x.size() = 3
//. . . etc
ints[0] = 2;
ints.at(2) = 4;
Enum Constants
Enums allow us to pass around identifiers, classifications, indicators etc in
text representation, while behind the scenes, the compiler recognizes them
as integers.
As an example, we can create an enum called `OptionType` that will
indicate the types of option deals that are allowed in a simple trading
system, eg European, American, Bermudan, and Asian. The `enum` type is
declared; then, inside the braces, the allowable types are defined, separated
by commas. By default, each will be assigned an integer value starting at
zero and incremented by one (remember that indexing in C++ is zero-
based). The closing brace must be followed by a semicolon. In code, we
would write:
enum OptionType
{
European, // default integer value = 0
American, // default integer value = 1
Bermudan, // default integer value = 2
Asian // default integer value = 3
};
We can then verify that in place of each option type, its corresponding
integer value is given:
European
American
Bermudan
Asian
So, we can see how the program treats the text representations as integers.
Note that these text labels are not enclosed in quotation marks, as they
ultimately represent integer types, not strings.
enum Baseball
{
Pitcher, // 0
Catcher, // 1
First_Baseman, // 2
Second_Baseman, // 3
Third_Baseman, // 4
Shortstop, // 5
Left_Field, // 6
Center_Field, // 7
Right_Field // 8
};
enum Football
{
Defensive_Tackle, // 0
Edge_Rusher, // 1
Defensive_End, // 2
Linebacker, // 3
Cornerback, // 4
Strong_Safety, // 5
Weak_Safety // 6
};
if (Defensive_End == First_Baseman)
{
cout << " Defensive_End == First_Baseman is true"
<< endl;
}
else
{
cout << " Defensive_End != First_Baseman is true"
<< endl;
}
enum Baseball
{
Pitcher = 100,
Catcher, // 101
First_Baseman, // 102
. . .
};
enum Football
{
Defensive_Tackle = 200,
Edge_Rusher, // 201
Defensive_End, // 202
. . .
};
Enum Classes
A new and more robust way to avoid `enum` overlaps was introduced in
C++11 that eliminates the integer representation altogether. The other
benefits of enums, such as avoiding cryptic numerical codes and larger
string objects, still remain, but the conflicts are avoided by using what is
called an enum class. As an example, we can define bond and futures
contract categories within enum classes, as shown here:
if(Bond::Corporate == Futures_Contract::Gold)
{
// . . .
}
Control Structures
Control structures consist of two categories:
Conditional Branching
C++ supports both the usual `if` based logic found in most other languages,
and `switch`/`case` statements that offer a cleaner alternative to multiple
`else if` conditions in special cases.
// Simple if
if (condition)
{
// action
}
// if/else
if (condition)
{
// action
}
else
{
// default action
}
// if/else if.../else
if (condition 1)
{
// action 1
}
else if (condition 2)
{
// action 2
}
// ...
else if (condition n)
{
// action n
}
else
{
// default action
}
TIP
In conditional statements containing `else if`, it is a best practice to include a default
`else` block at the end. Without it, code may build without any complaints from the
compiler and run just fine, but its execution could very easily result in unexpected
behavior that can cause major headaches in larger and more realistic code bases.
Utilizing the inequality operators introduced above, we can then write some
simple examples with all three variations on the `if` statement theme:
int x = 1;
int y = 2;
int z = 3;
// Simple if
if (x > 0)
{
cout << x << " > 0" << endl;
}
// if/else
if (x >= y)
{
cout << x << " >= " << y << endl;
}
else
{
cout << x << " is less than " << y << endl;
}
// if/else if.../else
if (x == z)
{
cout << x << " == " << z << endl;
}
else if (x < z)
{
cout << x << " > " << z << endl;
}
else if (x > z)
{
cout << x << " < " << z << endl;
}
else
{
cout << "Default condition" << endl;
}
WARNING
Due to the nature of floating point numerical representation and arithmetic, one should
never test for exact equality between two `double` types, nor should floating point types
be compared identically to zero. These cases will be covered later in a separate context.
The operators for logical AND and OR can also be used within conditional
arguments. For example:
#include <cmath>
using std::abs;
using std::exp;
// Simple if
if (x > 0 || y < 1)
{
cout << x << " > 0 OR " << y << " < 1 " << endl;
}
// if/else if.../else
if (x > 0 && y < 1)
{
cout << x << " > 0 AND " << y << " < 1 " << endl;
}
else if (x <= 0 || y >= 1)
{
cout << x << " <= 0 OR " << y << " >= 1 " << endl;
}
else if (z <= 0 || (abs(x) > z && exp(y) < z))
{
cout << z << " <= 0 OR " << endl;
cout << abs(x) << " > " << z << " AND "
<< exp(y) << " < " << z << endl;
}
else
{
cout << "Default condition" << endl;
}
Note that in the last `else if` condition, we put the AND condition inside
round brackets, as OR takes precedence over AND. [cppreference.com]
Finally, we can assign logical conditions to `bool` variables, and used
within `if` conditions, as shown here:
Note that a boolean variable can be negated simply by preceding it with the
`!` operator, as shown in the first `else if` condition above.
WARNING
A common trap is to mistakenly use `=` to test equality instead of `==`. The former is
the assignment operator and will cause unexpected behavior in this case. Be sure to use
`==` when testing for equality.
using std::sin;
using std::cos;
int j = 10;
int k = 20;
double theta = 3.14;
double result = j < k ? sin(theta) : cos(theta);
void switch_statement(int x)
{
switch (x)
{
case 0:
cout << "European Option: Use Black-Scholes" <<
endl;
break;
case 1:
cout << "American Option: Use a lattice model" <<
endl;
break;
case 2:
cout << "Bermudan Option: Use Longstaff-Schwartz
Monte Carlo" << endl;
break;
case 3:
cout << "Asian Option: Calculate average of the
spot time series" << endl;
break;
default:
cout << "Option type unknown" << endl;
break;
}
}
After each case, the `break` statement instructs the program to exit the
`switch` statement once the corresponding code for a particular state is
executed. So if `x` is `1`, a lattice model would be called to price an
American option, and then control would pass out of the body of the
`switch` statement rather than checking if `x` is `2`.
There are also cases where one might want to drop down to the next step if
the same action is desired for multiple states. For example, in (American)
football, if a drive stalls, the offense punts the ball on fourth down and no
points are scored. If the team scores, however, it might have kicked a field
goal for three points, or scored a touchdown with three possible outcomes:
Miss the extra point(s) -- Result is six points
Kick the extra point -- Result is seven points
Score a two-point conversion -- Result is eight points
No matter how a team scores, it kicks the ball off to their opponent, so for
cases 3, 6, 7, and 8, we just drop down through each case until we hit the
kickoff. This quasi-Bayesian logic could then be implemented with the
following code:
void switch_football(int x)
{
switch (x)
{
case 0: // Drive stalls
cout << "Punt" << endl;
break;
case 3: // Kick field goal
case 6: // Score touchdown; miss extra point(s)
case 7: // Kick extra point
case 8: // Score two-point conversion
cout << "Kick off" << endl;
break;
default:
cout << "Are you at a tennis match?" << endl;
break;
}
}
However, modern ISO Guidelines now favor using enum classes, for the
reasons demonstrated above with integer conflicts. So, we just substitute the
`Options_Contract` enum class into the preceding example to get:
Iterative Statements
In C++, there are two built-in language features that enable looping logic
and iteration:
Our logical condition is for `i` to be strictly less than the value `max`. As
long as this condition holds, the value of `i` will be incremented
A `do...while` loop is similar, except that by placing the `while` condition at
the end, it guarantees that at least one iteration of the loop will be executed.
For example:
int i = 0;
int max = 10;
do
{
cout << i << ", ";
++i;
} while (i < max);
Note that even if `max` had been set to zero or less, there would still be one
trip through the `do...while` loop, as the maximum condition is not checked
until the end. This is the distinction that separates it from the simpler
`while` loop.
In time, we will see looping examples that involve more interesting
mathematics and financial applications.
The syntax here is important, namely the semicolons separating the three
expressions in the `for` argument. Breaking this down into parts a, b, and c,
we would have
for(a; b; c)
The results will be exactly the same as the those in the `while` loop
examples.
1. There technically is a difference between the pre- and post- increment
operator that can affect other uses, but either `++i` with `i++` in the
`for` will work identically. It is generally preferred to use `++i`
2. It is also legal to have a `for` loop where a decrement (`--`) is used to
decrease the index value down to some minimum value.
Once `i` is incremented to 11, the `if` statement is true, so the `break`
command is called, causing the program control to exit the `for` loop.
There is also the `continue` keyword that can be used to continue the
process of the loop, but since this is the default behavior of a loop anyway,
its usefulness is limited.
Nested Loops
In addition to nesting `if` conditions inside loops, it is also possible to nest
iterative blocks inside other blocks, whether they be `for` or `while` loop. In
quantitative programming, it is easy to find oneself writing double and
sometimes even triple nested loops when implementing common numerical
routines and financial models. This type of coding, however, can become
complex and error prone in a hurry, so one needs to take special
precautions, as well as consider alternatives we will take up later.
vector<double> v;
// Populate the vector v and then use below:
for(unsigned i = 0; i < v.size(); ++i)
{
// Do something with v[i] or v.at(i). . .
}
for(auto elem : v)
{
// Use elem, rather than v[i] or v.at(i)
}
And we are done. No worries about making a mistake with the index, there
is less to type, and the code more obviously expresses what it is doing. The
ISO Guidelines in fact tell us to prefer using range-based `for` loops with
`vector` objects, as well as other STL containers that will be discussed in
Chapter 7.
Aliases
Aliasing can take on several forms, the first being one of convenience,
namely type aliasing, where commonly used parameterized type names can
be assigned to a shorter and more descriptive alias names.
In addition, reference aliases help to avoid copies of objects being created
when they are passed into functions, often resulting in significant speedup
at runtime.
Pointers can also be considered as aliases, particularly useful for
representing an active object in class design (the `this` pointer in Chapter
4). Pointers (and now smart pointers) can also be used for allocating
memory that persists, but this is a separate and deeper discussion that will
be deferred until Chapter 6.
Both references and pointers can help facilitate the object-oriented
programming concepts of inheritance and composition that will be
presented in subsequent chapters.
Type Aliases
`std::vector<double>` objects are ubiquitous in quantitative code for fairly
obvious reasons. Because it is used so much, it is common to assign a type
alias to it, such as `RealVector`. This better expresses what it is
mathematically, plus we don’t need to bother with as much typing.
Using modern C++, we can define the alias `RealVector` by simply defining
it as follows:
As long as the alias is defined before it is used in the code, then it’s fair
game.
Prior to C++11, this application of the `using` command did not exist, so
type aliasing was accomplished by using the `typedef` command; eg,
This is also valid C++ and is still found in many modern code bases, but the
`using` form is preferable per the modern ISO Guidelines. The detailed
reason for this is outside the scope of this book, but the upshot is `using`
can be used to define aliases of generic templated types (eg, not just
`double` parameters as above), while `typedef` cannot.
References
A reference, put simply, provides an alias for a variable, rather than a type.
Once a reference is defined, then accessing or modifying it is exactly the
same as using the original variable. A reference is created by placing an
ampersand between the type name and the reference name before assigning
it to the original variable. For example:
int& ozone;
would be nonsense as there is nothing to which it refers, and the code would
fail to compile. Also, once a reference is defined, it cannot be reassigned to
another variable for the remainder of its lifetime.
Using a reference for a plain old numerical type is trivial, but they become
important when passing large objects into a function, so as to avoid object
copy that can decimate a program’s runtime performance.
Suppose we have a `std::vector` containing 2000 option contract objects?
By passing it as a reference into a function, the original object itself can be
accessed without copying it.
There is one caveat, however. Remember that if a reference is modified, so
is the original variable to which it refers. For this reason, one can just make
the reference argument `const`. Then, any attempt to modify the reference
will be prevented by the compiler.
For example, here are two functions that take in a `std::vector<int>` object
as a reference argument. The first one returns the sum of the elements, so
there is no modification of the elements attempted. The second one,
however, attempts to reset each element to twice its value and then sum the
elements. This will result in a compiler error – much better than a runtime
error – and prevent the operations from ever being executed:
// This is OK
using IntVector = std::vector<int>;
int sum_ints(const IntVector& v)
{
int sum = 0;
for (auto elem : v)
{
sum += elem;
}
return sum;
}
int sum_of_twice_the_ints(const IntVector& v)
{
// Will not compile! const prevents modification
// of the elements in the vector v.
int sum = 0;
for (auto elem : v)
{
elem = 2 * elem;
sum += elem;
}
return sum;
}
NOTE
It is also possible to pass a function argument as non-`const` reference, with the intent to
modify it in place. In this case, one would typically make the return type `void`, instead
of returning a modified variable. This is rarely justified anymore in modern C++ due to
return value optimization (RVO). With RVO, objects by default are returned “in place”
rather than as copies from functions. This is now a requirement for compilers per the
ISO standards, beginning with C++11.
One final point about references relates to managed languages such as Java and C#, in
that the default behavior is to pass objects by non-constant reference. In C++ the default
is to pass by value; hence, one must specifically instruct the compiler to expect a
function argument as a reference with the `&`. This is an adjustment that a programmer
needs to make if switching between C++ and a managed language.
Pointers
A pointer in C++ shares some similarities with a reference, in that it can
also be an alias to another variable, but rather than being permanently tied
to a variable throughout its lifetime, a pointer points to a memory address
containing the variable’s contents, and it can be redirected to another
memory address containing another variable.
This unfortunately can be confusing, as a memory address of a variable is
also indicated by the `&` operator, in addition to another operator `*` that is
used to declare a pointer. A simple example illustrates this. First, declare
and assign an integer variable:
int x = 42;
int* xp;
This says to create a variable that will be a pointer to an `int` type, but don’t
point to anything specific yet; this comes in the next step:
xp = &x;
The `&` operator in this case means the address of `x`. `xp` now points at
the memory address that contains the contents of `x`, namely 42. Note that
this usage of `&` has a different meaning than declaring a reference.
We can now access the contents of this memory address by dereferencing
`xp` by applying the `*` operator. If we put
the output would be 42, just as if we had applied `std::cout` to the variable
`x`. Note the `*` operator is used in a different context here, accessing the
contents of memory rather than declaring a pointer.
We can also means we can change the value of `x`. For example, putting
*xp = 25;
then both `*xp` and `x` will return the value 25, rather than 42.
It is also possible to reassign the pointer `xp` to a different memory address;
this is not possible to do with a reference. Suppose we have a different
integer variable `y` and we reassign `xp` to point to the address of `y`:
int y = 106;
xp = &y;
Now, `*xp` will return 106 rather than 25, but `x` is still equal to 25.
NOTE
`xp`, as opposed to `*xp`, will return the hexadecimal value that represents the address
of the first byte in memory containing the contents of `y`.
Similar to references, pointers can be used with objects. If we have a class
`SomeClass` with a member function, say `some_fcn`, then we can define a
pointer to a `SomeClass` object:
SomeClass sc;
auto SomeClass* ptr_sc = ≻
As it’s obvious that `ptr_sc` will point to a `SomeClass` object, we can use
the `auto` keyword without obscuring its context.
Suppose also that `SomeClass` has a member function `some_fcn`. This
function can be invoked by dereferencing `ptr_sc` and then calling it in the
usual way:
(*ptr_sc).some_fcn();
ptr_sc->some_fcn();
This is all we will need to know about pointers for now. More specifically,
these examples take place in stack memory, and they are automatically
deleted when the function or control block in which they are defined
terminates. More advanced usage will be presented later.
NOTE
Pointers can also point to memory allocated in heap memory, which allows the value or
object to persist in memory outside the scope of a function or control block. This
becomes relevant in certain situations related to object-oriented programming and
requires and extra care. Moreover, C++11 introduced smart pointers into the Standard
Library. These topics will be presented in Chapter 5.
Function Overloading
To illustrate function overloading, let’s look at an example of two versions
of a `sum` function, one of which returns a `double` type, while the other
returns a `vector<double>`. The first version is trivial, just summing two
real numbers.
#include <vector>
// . . .
double sum(double x, double y)
{
return x + y;
}
As we can see, the two functions perform two distinct tasks, and have
different return types, based on the types of arguments.
Overloaded functions can also be distinguished based on the number of
arguments of the same type, as well as return the same type. For example
(trivially), we could define a `sum` function that takes in three real
numbers:
sum(5.31, 92.26);
sum(4.19, 41.9, 419.0);
Operator Overloading
C++ provides the standard mathematical operators for integer and floating
type numerical values. The Standard Library also provides the `+` operator
for `std::string` types, which will concatenate them. However, there are no
operators provided for `std::vector`, for example. So, if we want to compute
an element-by-element sum of two vectors, or calculate a dot product, we’re
on our own.
We could just use the `sum` overload for two vectors as shown above for
vector addition, and write a new function called `dot_product` for vector
multiplication. However, C++ provides us with a more naturally
mathematical approach, namely operator overloading.
For a vector sum, the addition operator replaces the `sum` overload as
shown below. The body of the function remains the same:
std::vector<double> operator + (const std::vector<double>& x, const
std::vector<double>& y)
{
std::vector<double> add_vec;
if (x.size() == y.size())
{
for (unsigned i = 0; i < x.size(); ++i)
{
add_vec.push_back(x.at(i) + y.at(i));
}
}
return add_vec; // Empty vector if x & y sizes not
identical
}
Similarly, for the dot product, which returns a scalar (`double`), overload
the `*` operator:
NOTE
1. For simultaneous iteration over two `vector` objects, at this stage we need to
revert to an indexed `for` loop. There are more elegant ways to do this that avoid
the index but require additional background that will be presented in Chapter 7.
2. For the error condition where `x.size() != y.size()`, for now we are simply
returning an empty vector for the vector sum, and 0 for the dot product.
Exceptions would be more appropriate for production code.
Summary
This chapter has covered a fairly lengthy list of topics, starting with the
`std::vector` container class in the Standard Template Library (STL).
`std::vector` is ubiquitous in quantitative programming, for (good) reasons
that will be covered in Chapter 7, along with STL iterators and algorithms
that can make C++ code more elegant, reliable, and efficient. At this point,
however, the goal is to be familiar with `std::vector` as a dynamic array of
real numbers.
Aliases come in three different varieties: type aliases (`using`), references,
and pointers. `using` saves us from having to type out long type names,
such as `RealVector` in place of the oft-used `std::vector<double>`.
References in C++ are mostly used in passing `const` reference objects as
function arguments, avoiding object copying that can degrade performance,
while preventing the object from being modified inside the function.
Pointers have several important applications beyond being mere aliases that
will be presented in due course, along with smart pointers that were added
to the Standard Library beginning with C++11.
Function overloading is a natural fit for mathematical programming, and
operator overloading even more so for objects such as matrices and vectors
that are ubiquitous in quantitative programming. This is another topic that
will be extended in object-oriented programming in Chapter 4.
References
[1] CppReference:
`https://en.cppreference.com/w/cpp/language/operator_precedence]`
[2] Stroustrup 4E (not directly referenced)
About the Author(s)
John Doe does some interesting stuff...