Packages

math

Trigonometry, number theory, statistics and number formatting, with the results and edge cases you will meet in real calculations.

import "std:math"

std:math is for arithmetic that + - * / can't do: roots, powers, angles, primes, averages and spread. Reach for it when you are measuring, scaling, summarising a list of numbers or turning a number into text with a fixed number of decimals.

A few functions (floor, ceil, round, abs, min, max) are also built into the language and work without an import. The rest need import "std:math".

Roots, powers and rounding#

main.osl
import "std:math"

log math.sqrt(16)
log math.pow(2, 10)
log math.hypot(3, 4)
log math.cbrt(27)
log math.round(2.5) math.floor(2.7) math.ceil(2.1) math.trunc(-2.7)
Click the code to edit it

round rounds halves away from zero. floor goes down, ceil goes up, and trunc drops the fraction, so it rounds toward zero. That matters for negative numbers: floor(-2.7) is -3, trunc(-2.7) is -2.

There is no integer division operator in OSL, so floor(a / b) is how you divide and drop the remainder. math.mod(a, b) gives the remainder, with the sign of a:

main.osl
import "std:math"

log floor(17 / 5) math.mod(17, 5)
log math.mod(-7, 3)
Click the code to edit it

Constants#

math.pi, math.tau (two pi), math.e and math.phi (the golden ratio) are constants, not functions: no brackets.

main.osl
import "std:math"

auto radius = 3
log math.pi * (radius ^ 2)
log math.tau
Click the code to edit it

Angles#

The trigonometric functions take radians. Convert with math.radians and math.degrees:

main.osl
import "std:math"

log math.sin(math.radians(90))
log math.cos(0)
log math.degrees(math.pi)
log math.atan2(1, 1)
Click the code to edit it

atan2(y, x) is the angle of a point from the x axis, and unlike atan(y / x) it gets the quadrant right and doesn't divide by zero. math.hypot(x, y) is the distance from the origin.

Here is the distance between two points on a plane:

main.osl
import "std:math"

def distance(number x1, number y1, number x2, number y2) number (
  return math.hypot(x2 - x1, y2 - y1)
)

log distance(0, 0, 6, 8)
log distance(1, 1, 4, 5)
Click the code to edit it

Limiting and blending values#

clamp(value, min, max) keeps a value inside a range. lerp(start, end, t) moves between two values: t of 0 gives start, 1 gives end.

main.osl
import "std:math"

log math.clamp(15, 0, 10)
log math.clamp(-5, 0, 10)
log math.lerp(0, 100, 0.25)
log math.lerp(10, 20, 0.5)
log math.sign(-4) math.sign(0)
Click the code to edit it

Together they make a progress bar: math.clamp(done / total, 0, 1) can never leave the 0 to 1 range, however wrong the inputs are.

Percentages and fixed decimals#

math.toPercent(part, total) gives the percentage. math.toFixed(x, digits) returns text with exactly that many decimals, which is what you want for display, since a number alone can't remember trailing zeros:

main.osl
import "std:math"

auto pct = math.toPercent(1, 3)
log pct
log math.toFixed(pct, 1) ++ "%"
log math.toFixed(2, 3)
Click the code to edit it

Number theory#

gcd and lcm find the greatest common divisor and least common multiple. isPrime tests a number and primes(n) lists the first n primes:

main.osl
import "std:math"

log math.gcd(12, 18)
log math.lcm(4, 6).unwrap()
log math.isPrime(97) math.isPrime(91)
log math.primes(8)
Click the code to edit it

lcm, factorial and fibonacci return a result, because the answer can outgrow an int:

main.osl
import "std:math"

log math.factorial(10).unwrap()
log math.fibonacci(20).unwrap()

auto big = math.factorial(30)
log big.isErr() big.unwrapErr().kind
Click the code to edit it

A negative argument fails with a ValueError. Handle it as the errors lesson shows, with catch, switch, ! or unwrapOr.

Summarising a list#

The statistics functions take an array of numbers. sum and product can't fail (an empty array gives 0 and 1), so they return plain numbers:

main.osl
import "std:math"

number[] scores = [72, 85, 90, 64, 85]
log math.sum(scores)
log math.product([2, 3, 4])
Click the code to edit it

The functions that need at least one number (avg, median, maxOf, minOf, rangeOf and the rest) return a result, which fails with a ValueError on an empty array:

main.osl
import "std:math"

number[] scores = [72, 85, 90, 64, 85]
log math.avg(scores).unwrap()
log math.median(scores).unwrap()
log math.mode(scores)
log math.maxOf(scores).unwrap() math.minOf(scores).unwrap()
log math.rangeOf(scores).unwrap()

number[] none = []
auto e = math.avg(none).unwrapErr()
log e.kind ++ ": " ++ e.message
Click the code to edit it

mode returns an array because several values can tie, and an empty array is simply an empty list of modes, so it is the one that doesn't fail. A friendlier average is one line:

main.osl
import "std:math"

def average(number[] values) number (
  return math.avg(values).unwrapOr(0)
)

log average([4, 8])
log average([])
Click the code to edit it

Spread and position#

variance and standardDeviation describe how far values lie from the mean. They treat the numbers as a whole population unless you pass {sample: true}, which divides by one less than the count:

main.osl
import "std:math"

number[] data = [2, 4, 4, 4, 5, 5, 7, 9]
log math.variance(data).unwrap()
log math.standardDeviation(data).unwrap()
log math.standardDeviation(data, {sample: true}).unwrap()
Click the code to edit it

percentile(numbers, p) takes p from 0 to 100, quantile takes 0 to 1, and quartiles gives all three cut points at once:

main.osl
import "std:math"

number[] times = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
log math.percentile(times, 90).unwrap()
log math.quartiles(times).unwrap()
log math.iqr(times).unwrap()
Click the code to edit it

zScores says how many standard deviations each value is from the mean, which makes outliers easy to find. normalize rescales to 0 to 1:

main.osl
import "std:math"

log math.zScores([1, 2, 3])
log math.normalize([10, 20, 30])
Click the code to edit it

correlation and covariance compare two equally long arrays: math.correlation([1, 2, 3], [2, 4, 6]).unwrap() is 1, a perfect positive relationship.

NaN and infinity#

Some inputs have no real answer, and the result is NaN (not a number) instead of an error. NaN is never equal to anything, even itself, so test for it with a function:

main.osl
import "std:math"

auto bad = math.sqrt(-1)
log bad
log math.isNaN(bad)
log math.isFinite(5) math.isFinite(bad)
Click the code to edit it

math.isInfinite tests for the infinities. Dividing by zero with / throws a MathError rather than returning infinity, so a NaN mostly comes from the math functions: sqrt, log, asin and acos outside their domains.

Example: grade report#

A real use pulls several of these together: summarise scores, format them, and guard the empty case.

main.osl
import "std:math"

def report(string name, number[] scores) string (
  if scores.len == 0 return name ++ ": no scores"
  auto mean = math.avg(scores).unwrap()
  auto spread = math.standardDeviation(scores).unwrap()
  return name ++ ": mean " ++ math.toFixed(mean, 1) ++ ", spread " ++ math.toFixed(spread, 1)
)

log report("Ada", [90, 85, 100])
log report("Bo", [])
Click the code to edit it
Quick check

What does this print?

osl
auto a = math.sqrt(-1)
auto b = math.sqrt(-1)
log a == b
Your turn

Mean and range

Print the average of the temperatures to one decimal place, then the gap between the hottest and coldest day.

main.osl
Ctrl Enter runs
Expected output
19.8
8

API reference#

71 members, generated from the std:math source of OSL 9.3.0.

Functions

#math.abs(number x) number

abs returns the absolute value of x.

#math.acos(number x) number

acos returns the arccosine of x in radians, or NaN outside -1 to 1.

#math.asin(number x) number

asin returns the arcsine of x in radians, or NaN outside -1 to 1.

#math.atan(number x) number

atan returns the arctangent of x in radians.

#math.atan2(number y, number x) number

atan2 returns the angle in radians from the positive x axis to the point (x, y), from -pi to pi. Note that y comes first.

#math.avg(number[] numbers) result<number, error>

avg returns the arithmetic mean of numbers. Fails with ValueError for an empty array.

#math.bits(number x) int

bits returns the IEEE 754 binary64 encoding of x, reinterpreted as a signed 64-bit integer. fromBits(bits(x)) is x, including NaN payloads and -0.

#math.cbrt(number x) number

cbrt returns the cube root of x.

#math.ceil(number x) number

ceil rounds x up to the nearest whole number.

#math.clamp(number value, number min, number max) number

clamp limits value to the range from min to max. The bounds may come in either order.

#math.correlation(number[] a, number[] b) result<number, error>

correlation returns the Pearson correlation of two equally long arrays of paired values, from -1 to 1. Fails with ValueError for empty arrays, arrays of different lengths, or an array whose values are all equal, which has no correlation.

#math.cos(number x) number

cos returns the cosine of x radians.

#math.cosh(number x) number

cosh returns the hyperbolic cosine of x.

#math.covariance(number[] a, number[] b) result<number, error>

covariance returns the population covariance of two equally long arrays of paired values. Fails with ValueError for empty arrays or arrays of different lengths.

#math.degrees(number radians) number

degrees converts radians to degrees.

#math.exp(number x) number

exp returns e raised to the power x.

#math.factorial(int n) result<int, error>

factorial returns n! (1 * 2 * ... * n). Fails with ValueError for a negative n, and with OverflowError for one above 20, whose factorial doesn't fit an int.

#math.fibonacci(int n) result<int, error>

fibonacci returns the nth Fibonacci number, counting fibonacci(0) as 0 and fibonacci(1) as 1. Fails with ValueError for a negative n, and with OverflowError for one above 92, whose result doesn't fit an int.

#math.floor(number x) number

floor rounds x down to the nearest whole number.

#math.fma(number x, number y, number z) number

fma returns x * y + z rounded once, as IEEE 754 fusedMultiplyAdd does.

#math.fromBits(int bits) number

fromBits returns the number whose IEEE 754 binary64 encoding is bits.

#math.fround(number x) number

fround matches JavaScript's Math.fround: the nearest float32 value, as a number.

#math.gcd(int a, int b) int

gcd returns the greatest common divisor of a and b, which is never negative; math.gcd(0, 0) is 0. The one result an int can't hold, 2^63 from the smallest int, wraps as int arithmetic does.

#math.geometricMean(number[] numbers) result<number, error>

geometricMean returns the nth root of the product of n numbers, which must all be above 0. Fails with ValueError for an empty array or a number that is 0 or less.

#math.harmonicMean(number[] numbers) result<number, error>

harmonicMean returns the count of numbers divided by the sum of their reciprocals; they must all be above 0. Fails with ValueError for an empty array or a number that is 0 or less.

#math.hypot(number x, number y) number

hypot returns the length of the hypotenuse, sqrt(x*x + y*y), without overflowing for large values.

#math.iqr(number[] numbers) result<number, error>

iqr returns the interquartile range of numbers, q3 minus q1. Fails with ValueError for an empty array.

#math.isFinite(number x) boolean

isFinite reports whether x is neither infinite nor NaN.

#math.isInfinite(number x) boolean

isInfinite reports whether x is positive or negative infinity; use math.sign(x) for which one.

#math.isNaN(number x) boolean

isNaN reports whether x is NaN (not a number).

#math.isPrime(number n) boolean

isPrime reports whether n is a prime number. Numbers below 2 and numbers with a fractional part are not prime.

#math.lcm(int a, int b) result<int, error>

lcm returns the least common multiple of a and b, which is never negative, or 0 when either is 0. Fails with OverflowError when the result doesn't fit an int.

#math.ldexp(number x, int exp) number

ldexp returns x * 2^exp, rounding once even when the result is subnormal.

#math.lerp(number start, number end, number t) number

lerp interpolates linearly from start to end: t of 0 gives start, 1 gives end, and values outside 0 to 1 extrapolate.

#math.log(number x) number

log returns the natural logarithm of x: NaN when x is negative and -Infinity when it is 0.

#math.log10(number x) number

log10 returns the base-10 logarithm of x.

#math.log2(number x) number

log2 returns the base-2 logarithm of x.

#math.max(number a, number b) number

max returns the larger of a and b.

#math.maxOf(number[] numbers) result<number, error>

maxOf returns the largest of numbers. Fails with ValueError for an empty array.

#math.median(number[] numbers) result<number, error>

median returns the middle value of numbers, or the mean of the two middle values for an even count. Fails with ValueError for an empty array.

#math.min(number a, number b) number

min returns the smaller of a and b.

#math.minOf(number[] numbers) result<number, error>

minOf returns the smallest of numbers. Fails with ValueError for an empty array.

#math.mod(number a, number b) number

mod returns the remainder of a divided by b, with the sign of a: math.mod(-7, 3) is -1. Throws ValueError when b is 0.

#math.mode(number[] numbers) number[]

mode returns the most frequent values in numbers, in ascending order: one value usually, several on a tie, and none for an empty array.

#math.normalize(number[] numbers) number[]

normalize rescales numbers to the range 0 to 1, mapping the smallest to 0 and the largest to 1. When the numbers are all equal, each becomes 0.

#math.percentile(number[] numbers, number p) result<number, error>

percentile returns the pth percentile of numbers, for p from 0 to 100, interpolating between the closest values: the 50th is the median. Throws ValueError for p outside 0 to 100, and fails with ValueError for an empty array.

#math.pow(number base, number exponent) number

pow returns base raised to the power exponent.

#math.primes(int count) int[]

primes returns the first count prime numbers, starting at 2. Throws ValueError for a negative count.

#math.product(number[] numbers) number

product returns numbers multiplied together, or 1 for an empty array.

#math.quantile(number[] numbers, number q) result<number, error>

quantile returns the qth quantile of numbers, for q from 0 to 1, the same as math.percentile(numbers, q * 100). Throws ValueError for q outside 0 to 1, and fails with ValueError for an empty array.

#math.quartiles(number[] numbers) result<{q1: number, q2: number, q3: number}, error>

quartiles returns the 25th, 50th and 75th percentiles of numbers as {q1, q2, q3}. Fails with ValueError for an empty array.

#math.radians(number degrees) number

radians converts degrees to radians.

#math.rangeOf(number[] numbers) result<number, error>

rangeOf returns the largest of numbers minus the smallest. Fails with ValueError for an empty array.

#math.round(number x) number

round rounds x to the nearest whole number, with halves rounded away from zero: 2.5 is 3 and -2.5 is -3.

#math.sign(number x) int

sign returns 1 for a positive x, -1 for a negative one and 0 for 0 or NaN.

#math.sin(number x) number

sin returns the sine of x radians.

#math.sinh(number x) number

sinh returns the hyperbolic sine of x.

#math.sqrt(number x) number

sqrt returns the square root of x, or NaN when x is negative.

#math.standardDeviation(number[] numbers, object? options) result<number, error>

standardDeviation returns the population standard deviation of numbers, the square root of math.variance. With the option sample: true it returns the sample standard deviation instead. Fails with ValueError for an empty array, or fewer than 2 numbers for a sample.

#math.sum(number[] numbers) number

sum returns the total of numbers, or 0 for an empty array.

#math.tan(number x) number

tan returns the tangent of x radians.

#math.tanh(number x) number

tanh returns the hyperbolic tangent of x.

#math.toFixed(number x, int digits) string

toFixed formats x with exactly digits digits after the decimal point, as C's printf("%.*f") does: the exact binary value of x is rounded to the nearest, with exact halves rounded to even, so math.toFixed(2.5, 0) is "2" and math.toFixed(0.125, 2) is "0.12". NaN and the infinities format as "NaN", "+Inf" and "-Inf". Throws ValueError for digits outside 0 to 100.

#math.toPercent(number x, number total) number

toPercent returns x as a percentage of total: math.toPercent(25, 200) is 12.5. Throws ValueError when total is 0.

#math.trunc(number x) number

trunc drops the fractional part of x, rounding toward zero.

#math.variance(number[] numbers, object? options) result<number, error>

variance returns the population variance of numbers: the mean squared difference from their mean. With the option sample: true it returns the sample variance instead, dividing by one less than the count. Fails with ValueError for an empty array, or fewer than 2 numbers for a sample.

#math.zScores(number[] numbers) number[]

zScores returns how many population standard deviations each of numbers is from their mean. When the numbers are all equal, every score is 0.

Constants

number math.e

E is Euler's number, the base of the natural logarithm, 2.718281828459045.

number math.phi

Phi is the golden ratio, 1.618033988749895.

number math.pi

Pi is the ratio of a circle's circumference to its diameter, 3.141592653589793.

number math.tau

Tau is a full turn in radians, 2 * pi.