Math Library
While the arithmetic and logic operators represent the basic operations that our [[CPU]] can perform, there are many other common math functions that com in handy. Since they are so common, programming languages usually have a math library that provides these functions. Logarithms, trigonometry and random number generation are just of few of the types of functions typically provided.
Math Constants
Math constants provide commonly used mathematical constanst to the highest precesion available. Some of the more useful math constants are summarized below.
| Constant | Description |
|---|---|
| M_E | Euler's constant [[ℯ]], base of the natural logarithm |
| M_PI | [[π]], Ratio of a circle's circumference to its diameter |
Math Functions
These most useful math functions are summarized below.
| Function | Description |
|---|---|
| abs(x) | [[Absolute value]] of x |
| acos(x) | [[Arc cosine]] of x, result is in the range [0,π] [[Radians]] |
| acosh(x) | [[Arc hyperbolic cosine]] of x |
| asin(x) | [[Arc sine]] of x, result is in the range [-π/2,π/2] [[Radians]] |
| asinh(x) | [[Arc hyperbolic sine]] of x |
| atan(x) | [[Arc tangent]] of x, result is in the range [-π/2,π/2] [[Radians]] |
| atan2(y,x) | Angle θ from the conversion of [[rectangular coordinates]] (x,y), result is in the range [-π,π] [[Radians]] |
| atanh(x) | [[Arc hyperbolic tangent]] of x |
| ceil(x) | Smallest integer value greater than or equal to x |
| cos(x) | [[Cosine]] of x (in [[Radians]]) |
| cosh(x) | [[Hyperbolic cosine]] of x |
| exp(x) | [[ℯ]] rasied to the power x, i.e. ℯx |
| floor(x) | Largest integer less than or equal to x |
| log(x) | [[Natural logarithm]] of x |
| log10(x) | [[Common logarithm]] of x |
| max(x,y) | Larger of x and y |
| min(x,y) | Smaller of x and y |
| pow(x,y) | x raised to the power y, i.e. xy |
| rand() | [[Pseudorandom]] number on the interval [0, RAND_MAX] |
| sin(x) | [[Sine]] of x (in [[Radians]]) |
| sinh(x) | [[Hyperbolic sine]] of x |
| sqrt(x) | [[Square root]] of x |
| tan(x) | [[Tangent]] of x (in [[Radians]]) |
| tanh(x) | [[Hyperbolic tangent]] of x |
Be sure to add the following include to the top of your program in order to use these math functions and constants.
#include <cmath>
#include <algorithm>
The program below illustrates the use of the floating point math functions.
/******************************************************************************
* This program demonstrates the math library.
*
* Copyright © 2016 Richard Lesh. All rights reserved.
*****************************************************************************/
#undef NDEBUG
#include <algorithm>
#include <cmath>
#include <fmt/format.h>
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char **argv) {
double const a = M_PI / 6;
double const b = M_PI / 4;
double const c = -a * 2;
double const d = -b * 2;
double const e = M_E;
cout << fmt::format("pi = {0:f}", M_PI) << endl;
cout << fmt::format("e = {0:f}", M_E) << endl;
// abs, floor, ceil, round, trunc, min, max
cout << fmt::format("abs({0:f}) = {1:f}", a, abs(a)) << endl;
cout << fmt::format("abs({0:f}) = {1:f}", c, abs(c)) << endl;
cout << fmt::format("floor({0:f}) = {1:f}", a, floor(a)) << endl;
cout << fmt::format("floor({0:f}) = {1:f}", c, floor(c)) << endl;
cout << fmt::format("ceil({0:f}) = {1:f}", a, ceil(a)) << endl;
cout << fmt::format("ceil({0:f}) = {1:f}", c, ceil(c)) << endl;
cout << fmt::format("round({0:f}) = {1:f}", a, floor(a + 0.5)) << endl;
cout << fmt::format("round({0:f}) = {1:f}", c, floor(c + 0.5)) << endl;
cout << fmt::format("trunc({0:f}) = {1:f}", a, trunc(a)) << endl;
cout << fmt::format("trunc({0:f}) = {1:f}", c, trunc(c)) << endl;
cout << fmt::format("min({0:f}, {1:f}) = {2:f}", a, c, min(a, c)) << endl;
cout << fmt::format("max({0:f}, {1:f}) = {2:f}", a, c, max(a, c)) << endl;
// sin, cos, tan, atan, atan2, acos, asin
cout << fmt::format("sin({0:f}) = {1:f}", a, sin(a)) << endl;
cout << fmt::format("sin({0:f}) = {1:f}", b, sin(b)) << endl;
cout << fmt::format("sin({0:f}) = {1:f}", c, sin(c)) << endl;
cout << fmt::format("sin({0:f}) = {1:f}", d, sin(d)) << endl;
cout << fmt::format("cos({0:f}) = {1:f}", a, cos(a)) << endl;
cout << fmt::format("cos({0:f}) = {1:f}", b, cos(b)) << endl;
cout << fmt::format("cos({0:f}) = {1:f}", c, cos(c)) << endl;
cout << fmt::format("cos({0:f}) = {1:f}", d, cos(d)) << endl;
cout << fmt::format("tan({0:f}) = {1:f}", a, tan(a)) << endl;
cout << fmt::format("tan({0:f}) = {1:f}", b, tan(b)) << endl;
cout << fmt::format("tan({0:f}) = {1:f}", c, tan(c)) << endl;
cout << fmt::format("asin({0:f}) = {1:f}", sin(a), asin(sin(a))) << endl;
cout << fmt::format("asin({0:f}) = {1:f}", sin(b), asin(sin(b))) << endl;
cout << fmt::format("asin({0:f}) = {1:f}", sin(c), asin(sin(c))) << endl;
cout << fmt::format("asin({0:f}) = {1:f}", sin(d), asin(sin(d))) << endl;
cout << fmt::format("acos({0:f}) = {1:f}", cos(a), acos(cos(a))) << endl;
cout << fmt::format("acos({0:f}) = {1:f}", cos(b), acos(cos(b))) << endl;
cout << fmt::format("acos({0:f}) = {1:f}", cos(c), acos(cos(c))) << endl;
cout << fmt::format("acos({0:f}) = {1:f}", cos(d), acos(cos(d))) << endl;
cout << fmt::format("atan({0:f}) = {1:f}", tan(a), atan(tan(a))) << endl;
cout << fmt::format("atan({0:f}) = {1:f}", tan(b), atan(tan(b))) << endl;
cout << fmt::format("atan({0:f}) = {1:f}", tan(c), atan(tan(c))) << endl;
// 45 degrees
cout << fmt::format("atan2({0:f}, {1:f}) = {2:f}", 1.0, 1.0, atan2(1.0, 1.0)) << endl;
// 30 degrees
cout << fmt::format("atan2({0:f}, {1:f}) = {2:f}", 1.0, sqrt(3.0), atan2(1.0, sqrt(3.0))) << endl;
// sinh, cosh, tanh, atanh, acosh, asinh
cout << fmt::format("sinh({0:f}) = {1:f}", a, sinh(a)) << endl;
cout << fmt::format("sinh({0:f}) = {1:f}", b, sinh(b)) << endl;
cout << fmt::format("sinh({0:f}) = {1:f}", c, sinh(c)) << endl;
cout << fmt::format("sinh({0:f}) = {1:f}", d, sinh(d)) << endl;
cout << fmt::format("cosh({0:f}) = {1:f}", a, cosh(a)) << endl;
cout << fmt::format("cosh({0:f}) = {1:f}", b, cosh(b)) << endl;
cout << fmt::format("cosh({0:f}) = {1:f}", c, cosh(c)) << endl;
cout << fmt::format("cosh({0:f}) = {1:f}", d, cosh(d)) << endl;
cout << fmt::format("tanh({0:f}) = {1:f}", a, tanh(a)) << endl;
cout << fmt::format("tanh({0:f}) = {1:f}", b, tanh(b)) << endl;
cout << fmt::format("tanh({0:f}) = {1:f}", c, tanh(c)) << endl;
cout << fmt::format("tanh({0:f}) = {1:f}", d, tanh(d)) << endl;
cout << fmt::format("asinh({0:f}) = {1:f}", sinh(a), asinh(sinh(a))) << endl;
cout << fmt::format("asinh({0:f}) = {1:f}", sinh(b), asinh(sinh(b))) << endl;
cout << fmt::format("asinh({0:f}) = {1:f}", sinh(c), asinh(sinh(c))) << endl;
cout << fmt::format("asinh({0:f}) = {1:f}", sinh(d), asinh(sinh(d))) << endl;
cout << fmt::format("acosh({0:f}) = {1:f}", cosh(a), acosh(cosh(a))) << endl;
cout << fmt::format("acosh({0:f}) = {1:f}", cosh(b), acosh(cosh(b))) << endl;
cout << fmt::format("acosh({0:f}) = {1:f}", cosh(c), acosh(cosh(c))) << endl;
cout << fmt::format("acosh({0:f}) = {1:f}", cosh(d), acosh(cosh(d))) << endl;
cout << fmt::format("atanh({0:f}) = {1:f}", tanh(a), atanh(tanh(a))) << endl;
cout << fmt::format("atanh({0:f}) = {1:f}", tanh(b), atanh(tanh(b))) << endl;
cout << fmt::format("atanh({0:f}) = {1:f}", tanh(c), atanh(tanh(c))) << endl;
cout << fmt::format("atanh({0:f}) = {1:f}", tanh(d), atanh(tanh(d))) << endl;
// log, log10, exp, pow, sqrt
cout << fmt::format("log({0:f}) = {1:f}", a, log(a)) << endl;
cout << fmt::format("log({0:f}) = {1:f}", b, log(b)) << endl;
cout << fmt::format("log({0:f}) = {1:f}", -c, log(-c)) << endl;
cout << fmt::format("log({0:f}) = {1:f}", -d, log(-d)) << endl;
cout << fmt::format("log({0:f}) = {1:f}", e, log(e)) << endl;
cout << fmt::format("log10({0:f}) = {1:f}", a, log10(a)) << endl;
cout << fmt::format("log10({0:f}) = {1:f}", b, log10(b)) << endl;
cout << fmt::format("log10({0:f}) = {1:f}", -c, log10(-c)) << endl;
cout << fmt::format("log10({0:f}) = {1:f}", -d, log10(-d)) << endl;
cout << fmt::format("log10({0:f}) = {1:f}", e, log10(e)) << endl;
cout << fmt::format("exp({0:f}) = {1:f}", 0.5, exp(0.5)) << endl;
cout << fmt::format("exp({0:f}) = {1:f}", 1.0, exp(1.0)) << endl;
cout << fmt::format("exp({0:f}) = {1:f}", 2.0, exp(2.0)) << endl;
cout << fmt::format("pow({0:f}, {1:f}) = {2:f}", 10.0, 0.5, pow(10.0, 0.5)) << endl;
cout << fmt::format("pow({0:f}, {1:f}) = {2:f}", 10.0, 1.0, pow(10.0, 1.0)) << endl;
cout << fmt::format("pow({0:f}, {1:f}) = {2:f}", 10.0, 2.0, pow(10.0, 2.0)) << endl;
cout << fmt::format("sqrt({0:f}) = {1:f}", 0.5, sqrt(0.5)) << endl;
cout << fmt::format("sqrt({0:f}) = {1:f}", 2.0, sqrt(2.0)) << endl;
cout << fmt::format("sqrt({0:f}) = {1:f}", 10.0, sqrt(10.0)) << endl;
// random numbers
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
return 0;
}
Output
The program below illustrates the use of the integer math and random number functions.
/******************************************************************************
* This program demonstrates the math integer functions.
*
* Copyright © 2020 Richard Lesh. All rights reserved.
*****************************************************************************/
#undef NDEBUG
#include <algorithm>
#include <cmath>
#include <ctime>
#include <fmt/format.h>
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char **argv) {
int const a = 5;
int const b = 10;
int const c = -2;
// abs, floor, ceil, round, trunc, min, max
cout << fmt::format("abs({0:d}) = {1:d}", a, abs(a)) << endl;
cout << fmt::format("abs({0:d}) = {1:d}", c, abs(c)) << endl;
cout << fmt::format("min({0:d}, {1:d}) = {2:d}", a, b, min(a, b)) << endl;
cout << fmt::format("max({0:d}, {1:d}) = {2:d}", a, b, max(a, b)) << endl;
cout << fmt::format("min({0:d}, {1:d}) = {2:d}", b, c, min(b, c)) << endl;
cout << fmt::format("max({0:d}, {1:d}) = {2:d}", b, c, max(b, c)) << endl;
// random numbers
srand(time(0));
cout << fmt::format("random({0:d}) = {1:d}", a, int(a * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", a, int(a * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", a, int(a * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", a, int(a * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", a, int(a * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", b, int(b * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", b, int(b * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", b, int(b * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", b, int(b * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random({0:d}) = {1:d}", b, int(b * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random(2) = {0:d}", int(2 * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random(2) = {0:d}", int(2 * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random(2) = {0:d}", int(2 * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random(2) = {0:d}", int(2 * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random(2) = {0:d}", int(2 * (rand()/(RAND_MAX + 1.0)))) << endl;
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
cout << fmt::format("random() = {0:f}", rand()/(RAND_MAX + 1.0)) << endl;
return 0;
}
Output
Random Numbers
Random number generation is an important technique needed for simulations and games. Computers can't actually generate true random numbers, so we have to settle for [[pseudorandom]] numbers, i.e. numbers generated deterministically but hopefully in an unpredictable manner.
In modern C++, we use the <random> library. It separates the job into two parts:
- an engine that produces pseudorandom bits
- a distribution that maps those bits into the shape you want
To generate the the pseudorandom bits we can use an algorithm called the [[Mersenne Twister]] which is implemented in the standard library mt19937 class.
For the three most common distribution cases of uniform integers, uniform floats and normally distributed ([[Gaussian]]) floats we use the following distribution classes:
- uniform integers → std::uniform_int_distribution
- uniform floats → std::uniform_real_distribution
- normal (Gaussian) floats → std::normal_distribution
For our example program we generate 10 numbers from each of the three distributions. Notice how each time we run the program, we will get different values.
#include <iostream>
#include <iomanip>
#include <random>
using namespace std;
int main() {
random_device rd;
mt19937 generator(rd());
uniform_int_distribution<int> dist1(1, 6);
// uniform with low=1 and high=6
cout << "Uniform Integers [1,6]" << endl;
for (int i = 0; i < 10; ++i) {
cout << dist1(generator) << endl;
}
// uniform with low=0.0 and high<1.0
uniform_real_distribution<double> dist2(0.0, 1.0);
cout << "Uniform Doubles [0.0, 1.0)" << endl;
for (int i = 0; i < 10; ++i) {
cout << fixed << setprecision(6)
<< dist2(generator) << endl;
}
// normal with mean 0.0, std. dev. 1.0
std::normal_distribution<double> dist3(0.0, 1.0);
cout << "Standard Normal" << endl;
for (int i = 0; i < 10; ++i) {
cout << fixed << setprecision(6)
<< dist3(generator) << endl;
}
}
Output
The uniform integer distribution produces integers uniformly in the range: [1, 6]. This means that both endpoints are included. That makes it perfect for dice rolls, random indices in a closed range, and similar cases.
The uniform floating point distribution produces doubles uniformly in the range: [0.0, 1.0). This means that only the low endpoint 0.0 is included. The high endpoint 1.0 will not ever be generated, i.e low <= random < high. That is commonly used for probabilities, simulation, Monte Carlo work, and scaling into another range.
This normal distribution generates values from a normal distribution with:
mean = 0.0 and standard deviation = 1.0.
That is called the standard normal distribution.
Most values cluster near the mean, and larger positive or negative values become less likely.
For example, to model exam scores centered around 75 with a standard deviation of 10:
std::normal_distribution.
Deterministic vs Non-Deterministic Seeding
If you want the same random sequence every run, use a fixed seed such as 12345. This is useful for debugging and testing.
std::mt19937 gen(12345);
If you want variation between runs use std::random_device. Which ever technique you choose, only seed the generator once, do not do it repeatedly in a loop.
Why not use rand()?
Older C and C++ code often uses: rand()
This is generally discouraged because:
- lower quality randomness
- awkward range scaling
- bias problems when using modulus operator (
%) - global shared state
- not flexible for distributions like normal
In modern C++ <random> is the right tool.
Questions
- {{Write an expression that yields √5.}}
- {{Write an expression that yields 3√5.}}
- {{Write an expression that yields the secant of π/4.}}
- {{Write an expression that yields log16 100.}}
Projects
More ★'s indicate higher difficulty level.
References
-
[[C++ Programming Language]], 4th Edition, Bjarne Stroustrup, Addison-Wesley, 2013, ISBN 978-0321563842.
- [[C++ Language Reference]]
- [[cplusplus.com]]
- [[Cprogramming.com]]
Pure Programmer


