Pure Programmer
Blue Matrix


Cluster Map

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.

C++ Math Constants
Constant Description
M_EEuler'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.

C++ Math Functions
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.

Math1.cpp
/******************************************************************************
 * 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
$ g++ -std=c++17 Math1.cpp -o Math1 -lfmt $ ./Math1 pi = 3.141593 e = 2.718282 abs(0.523599) = 0.523599 abs(-1.047198) = 1.047198 floor(0.523599) = 0.000000 floor(-1.047198) = -2.000000 ceil(0.523599) = 1.000000 ceil(-1.047198) = -1.000000 round(0.523599) = 1.000000 round(-1.047198) = -1.000000 trunc(0.523599) = 0.000000 trunc(-1.047198) = -1.000000 min(0.523599, -1.047198) = -1.047198 max(0.523599, -1.047198) = 0.523599 sin(0.523599) = 0.500000 sin(0.785398) = 0.707107 sin(-1.047198) = -0.866025 sin(-1.570796) = -1.000000 cos(0.523599) = 0.866025 cos(0.785398) = 0.707107 cos(-1.047198) = 0.500000 cos(-1.570796) = 0.000000 tan(0.523599) = 0.577350 tan(0.785398) = 1.000000 tan(-1.047198) = -1.732051 asin(0.500000) = 0.523599 asin(0.707107) = 0.785398 asin(-0.866025) = -1.047198 asin(-1.000000) = -1.570796 acos(0.866025) = 0.523599 acos(0.707107) = 0.785398 acos(0.500000) = 1.047198 acos(0.000000) = 1.570796 atan(0.577350) = 0.523599 atan(1.000000) = 0.785398 atan(-1.732051) = -1.047198 atan2(1.000000, 1.000000) = 0.785398 atan2(1.000000, 1.732051) = 0.523599 sinh(0.523599) = 0.547853 sinh(0.785398) = 0.868671 sinh(-1.047198) = -1.249367 sinh(-1.570796) = -2.301299 cosh(0.523599) = 1.140238 cosh(0.785398) = 1.324609 cosh(-1.047198) = 1.600287 cosh(-1.570796) = 2.509178 tanh(0.523599) = 0.480473 tanh(0.785398) = 0.655794 tanh(-1.047198) = -0.780714 tanh(-1.570796) = -0.917152 asinh(0.547853) = 0.523599 asinh(0.868671) = 0.785398 asinh(-1.249367) = -1.047198 asinh(-2.301299) = -1.570796 acosh(1.140238) = 0.523599 acosh(1.324609) = 0.785398 acosh(1.600287) = 1.047198 acosh(2.509178) = 1.570796 atanh(0.480473) = 0.523599 atanh(0.655794) = 0.785398 atanh(-0.780714) = -1.047198 atanh(-0.917152) = -1.570796 log(0.523599) = -0.647030 log(0.785398) = -0.241564 log(1.047198) = 0.046118 log(1.570796) = 0.451583 log(2.718282) = 1.000000 log10(0.523599) = -0.281001 log10(0.785398) = -0.104910 log10(1.047198) = 0.020029 log10(1.570796) = 0.196120 log10(2.718282) = 0.434294 exp(0.500000) = 1.648721 exp(1.000000) = 2.718282 exp(2.000000) = 7.389056 pow(10.000000, 0.500000) = 3.162278 pow(10.000000, 1.000000) = 10.000000 pow(10.000000, 2.000000) = 100.000000 sqrt(0.500000) = 0.707107 sqrt(2.000000) = 1.414214 sqrt(10.000000) = 3.162278 random() = 0.000008 random() = 0.131538 random() = 0.755605

The program below illustrates the use of the integer math and random number functions.

Math2.cpp
/******************************************************************************
 * 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
$ g++ -std=c++17 Math2.cpp -o Math2 -lfmt $ ./Math2 abs(5) = 5 abs(-2) = 2 min(5, 10) = 5 max(5, 10) = 10 min(10, -2) = -2 max(10, -2) = 10 random(5) = 3 random(5) = 1 random(5) = 4 random(5) = 3 random(5) = 3 random(10) = 2 random(10) = 9 random(10) = 5 random(10) = 7 random(10) = 8 random(2) = 1 random(2) = 0 random(2) = 1 random(2) = 0 random(2) = 1 random() = 0.766083 random() = 0.562219 random() = 0.212029 random() = 0.566925 random() = 0.305645

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:

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:

  1. uniform integers → std::uniform_int_distribution
  2. uniform floats → std::uniform_real_distribution
  3. 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.

RandomNumbers.cpp
#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
$ g++ -std=c++20 RandomNumbers.cpp -o RandomNumbers -lfmt $ ./RandomNumbers Uniform Integers [1,6] 4 4 1 3 4 6 5 4 4 1 Uniform Doubles [0.0, 1.0) 0.932911 0.779787 0.713604 0.360316 0.628266 0.477223 0.277357 0.173775 0.649073 0.099711 Standard Normal -0.467933 1.430076 0.866068 -0.902835 1.279065 -2.106423 -0.387189 -0.311939 -0.200748 1.999206

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 dist(75.0, 10.0);.

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:

In modern C++ <random> is the right tool.

Questions

Projects

More ★'s indicate higher difficulty level.

References