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.

Rust Math Constants
Constant Description
EEuler's constant [[ℯ]], base of the natural logarithm
PI[[π]], Ratio of a circle's circumference to its diameter

Math Functions

These most useful math functions are summarized below. In Rust the math functions are actually methods of the Double type instead of standalone functions as in other languages.

Rust Math Functions
Function Description
x.abs()[[Absolute value]] of x
x.acos()[[Arc cosine]] of x, result is in the range [0,π] [[Radians]]
x.acosh()[[Arc hyperbolic cosine]] of x
x.asin()[[Arc sine]] of x, result is in the range [-π/2,π/2] [[Radians]]
x.asinh()[[Arc hyperbolic sine]] of x
x.atan()[[Arc tangent]] of x, result is in the range [-π/2,π/2] [[Radians]]
y.atan2(x)Angle θ from the conversion of [[rectangular coordinates]] (x,y),
result is in the range [-π,π] [[Radians]]
x.atanh()[[Arc hyperbolic tangent]] of x
x.ceil()Smallest integer value greater than or equal to x
x.cos()[[Cosine]] of x (in [[Radians]])
x.cosh()[[Hyperbolic cosine]] of x
x.exp()[[ℯ]] raised to the power x, i.e. ℯx
x.floor()Largest integer less than or equal to x
x.ln()[[Natural logarithm]] of x
x.log(y)Logarithm (base y) of x
x.log10()[[Common logarithm]] of x
x.max(y)Larger of x and y
x.min(y)Smaller of x and y
x.powf(y)x raised to the power y, i.e. xy
rand::random::<T>()[[Pseudorandom]] value of type T (requires the [[rand crate]])
x.sin()[[Sine]] of x (in [[Radians]])
x.sinh()[[Hyperbolic sine]] of x
x.sqrt()[[Square root]] of x
x.tan()[[Tangent]] of x (in [[Radians]])
x.tanh()[[Hyperbolic tangent]] of x

To use these math constants you must put the following statements at the beginning of your program:
use std::f64::consts::{E, PI};

The program below illustrates the use of the floating point math functions.

Math1.rs
/******************************************************************************
 * This program demonstrates the math library.
 *
 * Copyright © 2016 Richard Lesh. All rights reserved.
 *****************************************************************************/

use std::f64::consts::{E, PI};

fn main() {
    let a: f64 = PI / 6.0;
    let b: f64 = PI / 4.0;
    let c: f64 = -a * 2.0;
    let d: f64 = -b * 2.0;

    println!("pi = {}", PI);
    println!("e = {}", E);

    // abs, floor, ceil, round, trunc, min, max
    println!("abs({}) = {}", a, a.abs());
    println!("abs({}) = {}", c, c.abs());
    println!("floor({}) = {}", a, a.floor());
    println!("floor({}) = {}", c, c.floor());
    println!("ceil({}) = {}", a, a.ceil());
    println!("ceil({}) = {}", c, c.ceil());
    println!("round({}) = {}", a, a.round());
    println!("round({}) = {}", c, c.round());
    println!("trunc({}) = {}", a, a.trunc());
    println!("trunc({}) = {}", c, c.trunc());
    println!("min({}, {}) = {}", a, c, a.min(c));
    println!("max({}, {}) = {}", a, c, a.max(c));

    // sin, cos, tan, atan, atan2, acos, asin
    println!("sin({}) = {}", a, a.sin());
    println!("sin({}) = {}", b, b.sin());
    println!("sin({}) = {}", c, c.sin());
    println!("sin({}) = {}", d, d.sin());
    println!("cos({}) = {}", a, a.cos());
    println!("cos({}) = {}", b, b.cos());
    println!("cos({}) = {}", c, c.cos());
    println!("cos({}) = {}", d, d.cos());
    println!("tan({}) = {}", a, a.tan());
    println!("tan({}) = {}", b, b.tan());
    println!("tan({}) = {}", c, c.tan());
    println!("asin({}) = {}", a.sin(), a.sin().asin());
    println!("asin({}) = {}", b.sin(), b.sin().asin());
    println!("asin({}) = {}", c.sin(), c.sin().asin());
    println!("asin({}) = {}", d.sin(), d.sin().asin());
    println!("acos({}) = {}", a.cos(), a.cos().acos());
    println!("acos({}) = {}", b.cos(), b.cos().acos());
    println!("acos({}) = {}", c.cos(), c.cos().acos());
    println!("acos({}) = {}", d.cos(), d.cos().acos());
    println!("atan({}) = {}", a.tan(), a.tan().atan());
    println!("atan({}) = {}", b.tan(), b.tan().atan());
    println!("atan({}) = {}", c.tan(), c.tan().atan());

    // 45 degrees
    println!("atan2({}, {}) = {}", 1.0f64, 1.0f64, 1.0f64.atan2(1.0f64));
    // 30 degrees
    println!(
        "atan2({}, {}) = {}",
        1.0f64,
        3.0f64.sqrt(),
        1.0f64.atan2(3.0f64.sqrt())
    );

    // sinh, cosh, tanh, atanh, acosh, asinh
    println!("sinh({}) = {}", a, a.sinh());
    println!("sinh({}) = {}", b, b.sinh());
    println!("sinh({}) = {}", c, c.sinh());
    println!("sinh({}) = {}", d, d.sinh());
    println!("cosh({}) = {}", a, a.cosh());
    println!("cosh({}) = {}", b, b.cosh());
    println!("cosh({}) = {}", c, c.cosh());
    println!("cosh({}) = {}", d, d.cosh());
    println!("tanh({}) = {}", a, a.tanh());
    println!("tanh({}) = {}", b, b.tanh());
    println!("tanh({}) = {}", c, c.tanh());
    println!("tanh({}) = {}", d, d.tanh());
    println!("asinh({}) = {}", a.sinh(), a.sinh().asinh());
    println!("asinh({}) = {}", b.sinh(), b.sinh().asinh());
    println!("asinh({}) = {}", c.sinh(), c.sinh().asinh());
    println!("asinh({}) = {}", d.sinh(), d.sinh().asinh());
    println!("acosh({}) = {}", a.cosh(), a.cosh().acosh());
    println!("acosh({}) = {}", b.cosh(), b.cosh().acosh());
    println!("acosh({}) = {}", c.cosh(), c.cosh().acosh());
    println!("acosh({}) = {}", d.cosh(), d.cosh().acosh());
    println!("atanh({}) = {}", a.tanh(), a.tanh().atanh());
    println!("atanh({}) = {}", b.tanh(), b.tanh().atanh());
    println!("atanh({}) = {}", c.tanh(), c.tanh().atanh());
    println!("atanh({}) = {}", d.tanh(), d.tanh().atanh());

    // log, log10, exp, pow, sqrt
    println!("log({}) = {}", a, a.ln());
    println!("log({}) = {}", b, b.ln());
    println!("log({}) = {}", -c, (-c).ln());
    println!("log({}) = {}", -d, (-d).ln());
    println!("log({}) = {}", E, E.ln());
    println!("log10({}) = {}", a, a.log10());
    println!("log10({}) = {}", b, b.log10());
    println!("log10({}) = {}", -c, (-c).log10());
    println!("log10({}) = {}", -d, (-d).log10());
    println!("log10({}) = {}", E, E.log10());
    println!("exp({}) = {}", 0.5f64, 0.5f64.exp());
    println!("exp({}) = {}", 1.0f64, 1.0f64.exp());
    println!("exp({}) = {}", 2.0f64, 2.0f64.exp());
    println!("pow({}, {}) = {}", 10.0f64, 0.5f64, 10.0f64.powf(0.5f64));
    println!("pow({}, {}) = {}", 10.0f64, 1.0f64, 10.0f64.powf(1.0f64));
    println!("pow({}, {}) = {}", 10.0f64, 2.0f64, 10.0f64.powf(2.0f64));
    println!("sqrt({}) = {}", 0.5f64, 0.5f64.sqrt());
    println!("sqrt({}) = {}", 2.0f64, 2.0f64.sqrt());
    println!("sqrt({}) = {}", 10.0f64, 10.0f64.sqrt());
}
Output
$ rustc Math1.rs Compiling Math1 v1.0.0 (/Users/rich/Desktop/Resources/pureprogrammer/rs/examples/rs_proj) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s $ ./Math1 pi = 3.141592653589793 e = 2.718281828459045 abs(0.5235987755982988) = 0.5235987755982988 abs(-1.0471975511965976) = 1.0471975511965976 floor(0.5235987755982988) = 0 floor(-1.0471975511965976) = -2 ceil(0.5235987755982988) = 1 ceil(-1.0471975511965976) = -1 round(0.5235987755982988) = 1 round(-1.0471975511965976) = -1 trunc(0.5235987755982988) = 0 trunc(-1.0471975511965976) = -1 min(0.5235987755982988, -1.0471975511965976) = -1.0471975511965976 max(0.5235987755982988, -1.0471975511965976) = 0.5235987755982988 sin(0.5235987755982988) = 0.49999999999999994 sin(0.7853981633974483) = 0.7071067811865475 sin(-1.0471975511965976) = -0.8660254037844386 sin(-1.5707963267948966) = -1 cos(0.5235987755982988) = 0.8660254037844387 cos(0.7853981633974483) = 0.7071067811865476 cos(-1.0471975511965976) = 0.5000000000000001 cos(-1.5707963267948966) = 0.00000000000000006123233995736766 tan(0.5235987755982988) = 0.5773502691896256 tan(0.7853981633974483) = 0.9999999999999999 tan(-1.0471975511965976) = -1.7320508075688767 asin(0.49999999999999994) = 0.5235987755982988 asin(0.7071067811865475) = 0.7853981633974482 asin(-0.8660254037844386) = -1.0471975511965976 asin(-1) = -1.5707963267948966 acos(0.8660254037844387) = 0.5235987755982988 acos(0.7071067811865476) = 0.7853981633974483 acos(0.5000000000000001) = 1.0471975511965976 acos(0.00000000000000006123233995736766) = 1.5707963267948966 atan(0.5773502691896256) = 0.5235987755982988 atan(0.9999999999999999) = 0.7853981633974483 atan(-1.7320508075688767) = -1.0471975511965976 atan2(1, 1) = 0.7853981633974483 atan2(1, 1.7320508075688772) = 0.5235987755982989 sinh(0.5235987755982988) = 0.5478534738880397 sinh(0.7853981633974483) = 0.8686709614860095 sinh(-1.0471975511965976) = -1.2493670505239751 sinh(-1.5707963267948966) = -2.3012989023072947 cosh(0.5235987755982988) = 1.1402383210764289 cosh(0.7853981633974483) = 1.3246090892520057 cosh(-1.0471975511965976) = 1.600286857702386 cosh(-1.5707963267948966) = 2.5091784786580567 tanh(0.5235987755982988) = 0.48047277815645156 tanh(0.7853981633974483) = 0.6557942026326724 tanh(-1.0471975511965976) = -0.7807144353592677 tanh(-1.5707963267948966) = -0.9171523356672744 asinh(0.5478534738880397) = 0.5235987755982988 asinh(0.8686709614860095) = 0.7853981633974482 asinh(-1.2493670505239751) = -1.0471975511965976 asinh(-2.3012989023072947) = -1.5707963267948966 acosh(1.1402383210764289) = 0.523598775598299 acosh(1.3246090892520057) = 0.7853981633974482 acosh(1.600286857702386) = 1.0471975511965974 acosh(2.5091784786580567) = 1.5707963267948966 atanh(0.48047277815645156) = 0.5235987755982988 atanh(0.6557942026326724) = 0.7853981633974483 atanh(-0.7807144353592677) = -1.0471975511965976 atanh(-0.9171523356672744) = -1.5707963267948972 log(0.5235987755982988) = -0.6470295833786549 log(0.7853981633974483) = -0.2415644752704905 log(1.0471975511965976) = 0.046117597181290375 log(1.5707963267948966) = 0.4515827052894548 log(2.718281828459045) = 1 log10(0.5235987755982988) = -0.28100137768950983 log10(0.7853981633974483) = -0.10491011863382856 log10(1.0471975511965976) = 0.02002861797447137 log10(1.5707963267948966) = 0.19611987703015263 log10(2.718281828459045) = 0.4342944819032518 exp(0.5) = 1.6487212707001282 exp(1) = 2.718281828459045 exp(2) = 7.38905609893065 pow(10, 0.5) = 3.1622776601683795 pow(10, 1) = 10 pow(10, 2) = 100 sqrt(0.5) = 0.7071067811865476 sqrt(2) = 1.4142135623730951 sqrt(10) = 3.1622776601683795

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

Math2.rs
/******************************************************************************
 * This program demonstrates the math integer and random functions.
 * 
 * Copyright © 2020 Richard Lesh.  All rights reserved.
 *****************************************************************************/

use rand::Rng;

fn irandom(max: isize) -> isize {
    let mut rng = rand::thread_rng();
    rng.gen_range(0..=max)
}

fn main() {
    let a: isize = 5;
    let b: isize = 10;
    let c: isize = -2;

    // abs, min, max
    println!("abs({}) = {}", a, a.abs());
    println!("abs({}) = {}", c, c.abs());
    println!("min({}, {}) = {}", a, b, a.min(b));
    println!("max({}, {}) = {}", a, b, a.max(b));
    println!("min({}, {}) = {}", b, c, b.min(c));
    println!("max({}, {}) = {}", b, c, b.max(c));

    // random integers
    println!("irandom({}) = {}", a, irandom(a));
    println!("irandom({}) = {}", a, irandom(a));
    println!("irandom({}) = {}", a, irandom(a));
    println!("irandom({}) = {}", b, irandom(b));
    println!("irandom({}) = {}", b, irandom(b));

    // random floating point values in [0, 1)
    println!("random() = {}", rand::random::<f64>());
    println!("random() = {}", rand::random::<f64>());
    println!("random() = {}", rand::random::<f64>());
}
Output
$ rustc Math2.rs Compiling Math2 v1.0.0 (/Users/rich/Desktop/Resources/pureprogrammer/rs/examples/rs_proj) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s $ ./Math2 abs(5) = 5 abs(-2) = 2 min(5, 10) = 5 max(5, 10) = 10 min(10, -2) = -2 max(10, -2) = 10 irandom(5) = 4 irandom(5) = 5 irandom(5) = 3 irandom(10) = 9 irandom(10) = 2 random() = 0.2587355320684718 random() = 0.8427004834793289 random() = 0.22231714860187746

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 Rust, random number generation is usually done with the rand crate. Since random number generation is not part of Rust’s standard library, Rust programs typically add external crates such as rand and rand_distr for more advanced probability distributions.

For the three most common cases of uniform integers, uniform floating point numbers, and normally distributed ([[Gaussian]]) floating point numbers, Rust commonly uses:

  1. uniform integers → rng.gen_range()
  2. uniform floating point numbers → rng.gen() or rng.gen_range()
  3. normal (Gaussian) floating point numbers → distributions such as Normal from rand_distr

For our example program we generate 10 numbers from each of the three distributions. Notice how each time we run the program, we will usually get different values.

RandomNumbers.rs
use rand::Rng;
use rand_distr::{Distribution, Normal};

fn main() {
    let mut rng = rand::thread_rng();
    let standard_normal = Normal::new(0.0, 1.0).unwrap();

    println!("Uniform integers in [1, 6]");
    for _ in 0..10 {
        println!("{}", rng.gen_range(1..7));
    }

    println!("Uniform floating point values in [0.0, 1.0)");
    for _ in 0..10 {
        println!("{}", rng.gen::<f64>());
    }

    println!("Standard Normal");
    for _ in 0..10 {
        println!("{}", standard_normal.sample(&mut rng));
    }
}
Output
$ rustc RandomNumbers.rs Compiling RandomNumbers v1.0.0 (/Users/rich/Desktop/Resources/pureprogrammer/rs/examples/rs_proj) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s $ ./RandomNumbers Uniform integers in [1, 6] 1 3 5 2 1 2 3 4 2 2 Uniform floating point values in [0.0, 1.0) 0.28870017753880484 0.5755656990778919 0.6633172748128991 0.5680317147097306 0.4164030453609826 0.80912808037652 0.8291518125958857 0.8826557389465475 0.614576454778801 0.22575239272173797 Standard Normal 1.167841485026413 0.3344212820187011 -1.6133890023702924 0.6139499714679905 -0.41591496437331543 -0.8534846955933978 0.3681877520679065 0.7671457278710387 -0.6644621864488376 1.388554855577877

The uniform integer generator can produce integers uniformly in a range such as: [1, 6].

In Rust, integer bounds are usually expressed with an inclusive lower bound and an exclusive upper bound. So to simulate a six-sided die we would write: rng.gen_range(1..7).

This means that 1 is included and 7 is excluded, so the possible values are 1 through 6. That makes it perfect for dice rolls, random indices in a half-open range, and similar cases.

The uniform floating point generator produces values 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 be generated, i.e. low <= random < high.

That is commonly used for probabilities, simulation, [[Monte Carlo Method]], and scaling into another range.

The Gaussian generator produces 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.

In Rust, Gaussian values are commonly generated with the Normal distribution from the rand_distr crate.

For example, to model exam scores centered around 75 with a standard deviation of 10, we can construct a distribution with mean 75.0 and standard deviation 10.0.

Deterministic vs Non-Deterministic Seeding

If you want the same random sequence every run, use a fixed seed. This is useful for debugging and testing.

use rand::SeedableRng;
use rand::rngs::StdRng;

let mut rng = StdRng::seed_from_u64(12345);

If you want variation between runs, use rand::thread_rng(). This gives you a generator automatically seeded from the operating system or another system source.

Whichever technique you choose, only seed the generator once. Do not recreate and reseed it repeatedly in a loop.

Range-Based Generation

In Rust, it is common to generate random values in a range using gen_range().

use rand::Rng;

let mut rng = rand::thread_rng();
let die_roll = rng.gen_range(1..7);
let x = rng.gen_range(10.0..20.0);

This makes it easy to generate random integers or floating point values in half-open ranges.

Gaussian Random Variables

Unlike Java, Rust does not provide Gaussian random values directly from a built-in generator in the standard library. Instead, this is usually handled through a probability distribution type such as Normal from rand_distr.

use rand_distr::{Distribution, Normal};

let normal = Normal::new(70.0, 10.0).unwrap();
let g = normal.sample(&mut rng);

This produces values from a normal distribution with mean 70.0 and standard deviation 10.0.

Why not use a built-in random() function?

Unlike Java’s Math.random(), Rust has no built-in standard-library random function.

This is intentional. Rust keeps the standard library small, and random number generation is handled by external crates.

This is acceptable and even desirable because:

For quick one-line examples, Rust code can still use helper methods such as rng.gen::<f64>(). But for serious simulation, games, testing, or reusable code, rand and rand_distr are usually the better tools.

Crate Dependencies

To use these examples in a Cargo project, add the following dependencies to your Cargo.toml file:

[dependencies]
rand = "0.8"
rand_distr = "0.4"

Questions

Projects

More ★'s indicate higher difficulty level.

References