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.

Perl Math Constants
Constant Description
exp(1)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.

Perl 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
floor(x+0.5)Nearest integer to x (round)
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,1)
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 use statements to the top of your program in order to use these math functions and constants.
use Math::Trig;
use List::Util qw[min max];
use POSIX;

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

Math1.pl
#!/usr/bin/env perl
use utf8;
###############################################################################
# This program demonstrates the math library.
# 
# Copyright © 2016 Richard Lesh.  All rights reserved.
###############################################################################

use List::Util qw(min max);
use Math::Trig qw(abs acos acosh asin asinh atan atan2 atanh cosh pi sinh tan tanh);
use POSIX qw(ceil floor log10);
use Utils;
use strict;
use warnings;

MAIN:
{
	my $a = pi() / 6;
	my $b = pi() / 4;
	my $c = -$a * 2;
	my $d = -$b * 2;
	my $e = exp(1.);

	print Utils::messageFormat("pi = \{0:f\}", pi()), "\n";
	print Utils::messageFormat("e = \{0:f\}", exp(1.)), "\n";

# abs, floor, ceil, round, trunc, min, max
	print Utils::messageFormat("abs(\{0:f\}) = \{1:f\}", $a, abs($a)), "\n";
	print Utils::messageFormat("abs(\{0:f\}) = \{1:f\}", $c, abs($c)), "\n";
	print Utils::messageFormat("floor(\{0:f\}) = \{1:f\}", $a, floor($a)), "\n";
	print Utils::messageFormat("floor(\{0:f\}) = \{1:f\}", $c, floor($c)), "\n";
	print Utils::messageFormat("ceil(\{0:f\}) = \{1:f\}", $a, ceil($a)), "\n";
	print Utils::messageFormat("ceil(\{0:f\}) = \{1:f\}", $c, ceil($c)), "\n";
	print Utils::messageFormat("round(\{0:f\}) = \{1:f\}", $a, floor($a + 0.5)), "\n";
	print Utils::messageFormat("round(\{0:f\}) = \{1:f\}", $c, floor($c + 0.5)), "\n";
	print Utils::messageFormat("trunc(\{0:f\}) = \{1:f\}", $a, $a < 0.0 ? ceil($a) : floor($a)), "\n";
	print Utils::messageFormat("trunc(\{0:f\}) = \{1:f\}", $c, $c < 0.0 ? ceil($c) : floor($c)), "\n";
	print Utils::messageFormat("min(\{0:f\}, \{1:f\}) = \{2:f\}", $a, $c, min($a, $c)), "\n";
	print Utils::messageFormat("max(\{0:f\}, \{1:f\}) = \{2:f\}", $a, $c, max($a, $c)), "\n";

# sin, cos, tan, atan, atan2, acos, asin
	print Utils::messageFormat("sin(\{0:f\}) = \{1:f\}", $a, sin($a)), "\n";
	print Utils::messageFormat("sin(\{0:f\}) = \{1:f\}", $b, sin($b)), "\n";
	print Utils::messageFormat("sin(\{0:f\}) = \{1:f\}", $c, sin($c)), "\n";
	print Utils::messageFormat("sin(\{0:f\}) = \{1:f\}", $d, sin($d)), "\n";
	print Utils::messageFormat("cos(\{0:f\}) = \{1:f\}", $a, cos($a)), "\n";
	print Utils::messageFormat("cos(\{0:f\}) = \{1:f\}", $b, cos($b)), "\n";
	print Utils::messageFormat("cos(\{0:f\}) = \{1:f\}", $c, cos($c)), "\n";
	print Utils::messageFormat("cos(\{0:f\}) = \{1:f\}", $d, cos($d)), "\n";
	print Utils::messageFormat("tan(\{0:f\}) = \{1:f\}", $a, tan($a)), "\n";
	print Utils::messageFormat("tan(\{0:f\}) = \{1:f\}", $b, tan($b)), "\n";
	print Utils::messageFormat("tan(\{0:f\}) = \{1:f\}", $c, tan($c)), "\n";
	print Utils::messageFormat("asin(\{0:f\}) = \{1:f\}", sin($a), asin(sin($a))), "\n";
	print Utils::messageFormat("asin(\{0:f\}) = \{1:f\}", sin($b), asin(sin($b))), "\n";
	print Utils::messageFormat("asin(\{0:f\}) = \{1:f\}", sin($c), asin(sin($c))), "\n";
	print Utils::messageFormat("asin(\{0:f\}) = \{1:f\}", sin($d), asin(sin($d))), "\n";
	print Utils::messageFormat("acos(\{0:f\}) = \{1:f\}", cos($a), acos(cos($a))), "\n";
	print Utils::messageFormat("acos(\{0:f\}) = \{1:f\}", cos($b), acos(cos($b))), "\n";
	print Utils::messageFormat("acos(\{0:f\}) = \{1:f\}", cos($c), acos(cos($c))), "\n";
	print Utils::messageFormat("acos(\{0:f\}) = \{1:f\}", cos($d), acos(cos($d))), "\n";
	print Utils::messageFormat("atan(\{0:f\}) = \{1:f\}", tan($a), atan(tan($a))), "\n";
	print Utils::messageFormat("atan(\{0:f\}) = \{1:f\}", tan($b), atan(tan($b))), "\n";
	print Utils::messageFormat("atan(\{0:f\}) = \{1:f\}", tan($c), atan(tan($c))), "\n";
# 45 degrees
	print Utils::messageFormat("atan2(\{0:f\}, \{1:f\}) = \{2:f\}", 1.0, 1.0, atan2(1.0, 1.0)), "\n";
# 30 degrees
	print Utils::messageFormat("atan2(\{0:f\}, \{1:f\}) = \{2:f\}", 1.0, sqrt(3.0), atan2(1.0, sqrt(3.0))), "\n";

# sinh, cosh, tanh, atanh, acosh, asinh
	print Utils::messageFormat("sinh(\{0:f\}) = \{1:f\}", $a, sinh($a)), "\n";
	print Utils::messageFormat("sinh(\{0:f\}) = \{1:f\}", $b, sinh($b)), "\n";
	print Utils::messageFormat("sinh(\{0:f\}) = \{1:f\}", $c, sinh($c)), "\n";
	print Utils::messageFormat("sinh(\{0:f\}) = \{1:f\}", $d, sinh($d)), "\n";
	print Utils::messageFormat("cosh(\{0:f\}) = \{1:f\}", $a, cosh($a)), "\n";
	print Utils::messageFormat("cosh(\{0:f\}) = \{1:f\}", $b, cosh($b)), "\n";
	print Utils::messageFormat("cosh(\{0:f\}) = \{1:f\}", $c, cosh($c)), "\n";
	print Utils::messageFormat("cosh(\{0:f\}) = \{1:f\}", $d, cosh($d)), "\n";
	print Utils::messageFormat("tanh(\{0:f\}) = \{1:f\}", $a, tanh($a)), "\n";
	print Utils::messageFormat("tanh(\{0:f\}) = \{1:f\}", $b, tanh($b)), "\n";
	print Utils::messageFormat("tanh(\{0:f\}) = \{1:f\}", $c, tanh($c)), "\n";
	print Utils::messageFormat("tanh(\{0:f\}) = \{1:f\}", $d, tanh($d)), "\n";
	print Utils::messageFormat("asinh(\{0:f\}) = \{1:f\}", sinh($a), asinh(sinh($a))), "\n";
	print Utils::messageFormat("asinh(\{0:f\}) = \{1:f\}", sinh($b), asinh(sinh($b))), "\n";
	print Utils::messageFormat("asinh(\{0:f\}) = \{1:f\}", sinh($c), asinh(sinh($c))), "\n";
	print Utils::messageFormat("asinh(\{0:f\}) = \{1:f\}", sinh($d), asinh(sinh($d))), "\n";
	print Utils::messageFormat("acosh(\{0:f\}) = \{1:f\}", cosh($a), acosh(cosh($a))), "\n";
	print Utils::messageFormat("acosh(\{0:f\}) = \{1:f\}", cosh($b), acosh(cosh($b))), "\n";
	print Utils::messageFormat("acosh(\{0:f\}) = \{1:f\}", cosh($c), acosh(cosh($c))), "\n";
	print Utils::messageFormat("acosh(\{0:f\}) = \{1:f\}", cosh($d), acosh(cosh($d))), "\n";
	print Utils::messageFormat("atanh(\{0:f\}) = \{1:f\}", tanh($a), atanh(tanh($a))), "\n";
	print Utils::messageFormat("atanh(\{0:f\}) = \{1:f\}", tanh($b), atanh(tanh($b))), "\n";
	print Utils::messageFormat("atanh(\{0:f\}) = \{1:f\}", tanh($c), atanh(tanh($c))), "\n";
	print Utils::messageFormat("atanh(\{0:f\}) = \{1:f\}", tanh($d), atanh(tanh($d))), "\n";

# log, log10, exp, pow, sqrt
	print Utils::messageFormat("log(\{0:f\}) = \{1:f\}", $a, log($a)), "\n";
	print Utils::messageFormat("log(\{0:f\}) = \{1:f\}", $b, log($b)), "\n";
	print Utils::messageFormat("log(\{0:f\}) = \{1:f\}", -$c, log(-$c)), "\n";
	print Utils::messageFormat("log(\{0:f\}) = \{1:f\}", -$d, log(-$d)), "\n";
	print Utils::messageFormat("log(\{0:f\}) = \{1:f\}", $e, log($e)), "\n";
	print Utils::messageFormat("log10(\{0:f\}) = \{1:f\}", $a, log10($a)), "\n";
	print Utils::messageFormat("log10(\{0:f\}) = \{1:f\}", $b, log10($b)), "\n";
	print Utils::messageFormat("log10(\{0:f\}) = \{1:f\}", -$c, log10(-$c)), "\n";
	print Utils::messageFormat("log10(\{0:f\}) = \{1:f\}", -$d, log10(-$d)), "\n";
	print Utils::messageFormat("log10(\{0:f\}) = \{1:f\}", $e, log10($e)), "\n";
	print Utils::messageFormat("exp(\{0:f\}) = \{1:f\}", 0.5, exp(0.5)), "\n";
	print Utils::messageFormat("exp(\{0:f\}) = \{1:f\}", 1.0, exp(1.0)), "\n";
	print Utils::messageFormat("exp(\{0:f\}) = \{1:f\}", 2.0, exp(2.0)), "\n";
	print Utils::messageFormat("pow(\{0:f\}, \{1:f\}) = \{2:f\}", 10.0, 0.5, ((10.0) ** (0.5))), "\n";
	print Utils::messageFormat("pow(\{0:f\}, \{1:f\}) = \{2:f\}", 10.0, 1.0, ((10.0) ** (1.0))), "\n";
	print Utils::messageFormat("pow(\{0:f\}, \{1:f\}) = \{2:f\}", 10.0, 2.0, ((10.0) ** (2.0))), "\n";
	print Utils::messageFormat("sqrt(\{0:f\}) = \{1:f\}", 0.5, sqrt(0.5)), "\n";
	print Utils::messageFormat("sqrt(\{0:f\}) = \{1:f\}", 2.0, sqrt(2.0)), "\n";
	print Utils::messageFormat("sqrt(\{0:f\}) = \{1:f\}", 10.0, sqrt(10.0)), "\n";

# random numbers
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
}

Output
$ perl Math1.pl "abs" is not exported by the Math::Trig module "atan2" is not exported by the Math::Trig module "pow" is not exported by the Math::Trig module "rand" is not exported by the Math::Trig module Can't continue after import errors at Math1.pl line 10. BEGIN failed--compilation aborted at Math1.pl line 10.

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

Math2.pl
#!/usr/bin/env perl
use utf8;
###############################################################################
# This program demonstrates the math integer functions.
# 
# Copyright © 2020 Richard Lesh.  All rights reserved.
###############################################################################

use List::Util qw(min max);
use Utils;
use strict;
use warnings;

MAIN:
{
	my $a = 5;
	my $b = 10;
	my $c = -2;

# abs, floor, ceil, round, trunc, min, max
	print Utils::messageFormat("abs(\{0:d\}) = \{1:d\}", $a, abs($a)), "\n";
	print Utils::messageFormat("abs(\{0:d\}) = \{1:d\}", $c, abs($c)), "\n";
	print Utils::messageFormat("min(\{0:d\}, \{1:d\}) = \{2:d\}", $a, $b, min($a, $b)), "\n";
	print Utils::messageFormat("max(\{0:d\}, \{1:d\}) = \{2:d\}", $a, $b, max($a, $b)), "\n";
	print Utils::messageFormat("min(\{0:d\}, \{1:d\}) = \{2:d\}", $b, $c, min($b, $c)), "\n";
	print Utils::messageFormat("max(\{0:d\}, \{1:d\}) = \{2:d\}", $b, $c, max($b, $c)), "\n";

# random numbers
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $a, int(rand($a))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $a, int(rand($a))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $a, int(rand($a))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $a, int(rand($a))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $a, int(rand($a))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $b, int(rand($b))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $b, int(rand($b))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $b, int(rand($b))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $b, int(rand($b))), "\n";
	print Utils::messageFormat("random(\{0:d\}) = \{1:d\}", $b, int(rand($b))), "\n";
	print Utils::messageFormat("random(2) = \{0:d\}", int(rand(2))), "\n";
	print Utils::messageFormat("random(2) = \{0:d\}", int(rand(2))), "\n";
	print Utils::messageFormat("random(2) = \{0:d\}", int(rand(2))), "\n";
	print Utils::messageFormat("random(2) = \{0:d\}", int(rand(2))), "\n";
	print Utils::messageFormat("random(2) = \{0:d\}", int(rand(2))), "\n";
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
	print Utils::messageFormat("random() = \{0:f\}", rand()), "\n";
}

Output
$ perl Math2.pl "abs" is not exported by the Math::Trig module "rand" is not exported by the Math::Trig module Can't continue after import errors at Math2.pl line 10. BEGIN failed--compilation aborted at Math2.pl line 10.

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 Perl, random number generation is usually done with the built-in rand function, optionally combined with srand to control seeding.

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

  1. uniform integers → an expression based on rand
  2. uniform floating point numbers → rand()
  3. normal (Gaussian) floating point numbers → a helper function such as the [[Box-Muller transform]]

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.pl
use strict;
use warnings;

sub random_int {
    my ($low, $high) = @_;
    return int(rand($high - $low)) + $low;
}

sub random_gaussian {
    my $u1 = 0.0;
    my $u2 = 0.0;

    # Avoid log(0)
    while ($u1 == 0.0) {
        $u1 = rand();
    }
    while ($u2 == 0.0) {
        $u2 = rand();
    }

    return sqrt(-2.0 * log($u1)) * cos(2.0 * 3.141592653589793 * $u2);
}

print "Uniform integers in [1, 6]\n";
for (my $i = 0; $i < 10; ++$i) {
    print random_int(1, 7), "\n";
}

print "Uniform floating point values in [0.0, 1.0)\n";
for (my $i = 0; $i < 10; ++$i) {
    print rand(), "\n";
}

print "Standard Normal\n";
for (my $i = 0; $i < 10; ++$i) {
    print random_gaussian(), "\n";
}
Output
$ perl RandomNumbers.pl Uniform integers in [1, 6] 2 2 5 5 1 3 2 3 5 5 Uniform floating point values in [0.0, 1.0) 0.170614341057256 0.62097354302157 0.287957222696594 0.500791190972237 0.844945630830349 0.476200537610673 0.0768600345771269 0.91955169790802 0.464924247999488 0.990556915597161 Standard Normal -1.14224387321856 0.53371661181561 0.56545949861334 -1.2446725765567 -0.824609689126855 -1.55405743326823 0.130048439598241 0.45470800486386 -1.22547149024517 -0.866669815087155

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

In Perl, a common way to simulate a six-sided die is: int(rand(6)) + 1.

This works by first generating a floating point number in the interval [0.0, 6.0), truncating it to one of the integers 0 through 5, and then adding 1 to shift the range to 1 through 6.

That makes it perfect for dice rolls, random indices, and similar cases.

The uniform floating point generator produces values uniformly in the range: [0.0, N).

If called with no argument, rand() produces a floating point number in the interval [0.0, 1.0). If called with an argument such as rand(10), it produces a value in [0.0, 10.0).

This means that only the low endpoint 0.0 is included. The high endpoint is not generated, i.e. low <= random < high.

You can scale this output to another range [low, high) with the formula low + (high - low) * rand().

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

Perl does not provide a built-in Gaussian random number generator in its core syntax. To generate values from a normal distribution with mean = 0.0 and standard deviation = 1.0, we usually write a helper function based on the [[Box-Muller transform]].

That produces values from 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, we can scale and shift the standard normal value like this: 75.0 + 10.0 * random_gaussian().

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.

srand(12345);

If you want variation between runs, Perl will normally seed automatically, but you can also call: srand(); to seed from the current time or another system-dependent source.

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

Integer Ranges

To generate integers in a half-open range [$low, $high), a common Perl expression is: int(rand($high - $low)) + $low.

my $die_roll = int(rand(6)) + 1;
my $index = int(rand($n));

This is the most common general-purpose way to generate random integers in Perl.

Why not just use rand everywhere directly?

Simple Perl code often uses: rand

This is acceptable for many cases, but it is more limited because:

For quick one-line examples, rand is fine. But for simulations, games, testing, or reusable code, helper functions are often the better tool.

Questions

Projects

More ★'s indicate higher difficulty level.

References