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 |
|---|---|
| E | Euler'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.
| 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.
/******************************************************************************
* 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
The program below illustrates the use of the integer math and random number functions.
/******************************************************************************
* 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
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:
- uniform integers →
rng.gen_range() - uniform floating point numbers →
rng.gen()orrng.gen_range() - normal (Gaussian) floating point numbers → distributions such as
Normalfromrand_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.
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
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:
- it keeps the core language and standard library smaller
- it allows the ecosystem to provide richer random-number tools through crates
- it supports many distributions and generator types beyond a single built-in function
- it makes explicit in the code when external randomness support is being used
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
- {{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
- [[Rust Language Reference]]
- [[Rust Compiler]]
Pure Programmer


