# math

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

This is the Markdown version of https://osl-lang.org/packages/math. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
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

```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)
```

Output:

```text
4
1024
5
3
3 2 3 -2
```

`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`:

```osl
import "std:math"

log floor(17 / 5) math.mod(17, 5)
log math.mod(-7, 3)
```

Output:

```text
3 2
-1
```

## Constants

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

```osl
import "std:math"

auto radius = 3
log math.pi * (radius ^ 2)
log math.tau
```

Output:

```text
28.274333882308138
6.283185307179586
```

## Angles

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

```osl
import "std:math"

log math.sin(math.radians(90))
log math.cos(0)
log math.degrees(math.pi)
log math.atan2(1, 1)
```

Output:

```text
1
1
180
0.7853981633974483
```

`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:

```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)
```

Output:

```text
10
5
```

## 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`.

```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)
```

Output:

```text
10
0
25
15
-1 0
```

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:

```osl
import "std:math"

auto pct = math.toPercent(1, 3)
log pct
log math.toFixed(pct, 1) ++ "%"
log math.toFixed(2, 3)
```

Output:

```text
33.33333333333333
33.3%
2.000
```

> **Note**
>
> Numbers are binary floating point, as in most languages: `0.1 + 0.2` is `0.30000000000000004`. Don't compare computed decimals with `==`. Round first, or compare the difference to a small tolerance.

## 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:

```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)
```

Output:

```text
6
12
true false
[2,3,5,7,11,13,17,19]
```

`lcm`, `factorial` and `fibonacci` return a [result](/packages/result.md), because the answer can outgrow an `int`:

```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
```

Output:

```text
3628800
6765
true OverflowError
```

A negative argument fails with a `ValueError`. Handle it as the [errors lesson](/learn/errors.md) 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:

```osl
import "std:math"

number[] scores = [72, 85, 90, 64, 85]
log math.sum(scores)
log math.product([2, 3, 4])
```

Output:

```text
396
24
```

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:

```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
```

Output:

```text
79.2
85
[85]
90 64
26
ValueError: math.avg: the array is empty
```

`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:

```osl
import "std:math"

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

log average([4, 8])
log average([])
```

Output:

```text
6
0
```

## 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:

```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()
```

Output:

```text
4
2
2.138089935299395
```

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

```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()
```

Output:

```text
9.1
{"q1":3.25,"q2":5.5,"q3":7.75}
4.5
```

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

```osl
import "std:math"

log math.zScores([1, 2, 3])
log math.normalize([10, 20, 30])
```

Output:

```text
[-1.224744871391589,0,1.224744871391589]
[0,0.5,1]
```

`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:

```osl
import "std:math"

auto bad = math.sqrt(-1)
log bad
log math.isNaN(bad)
log math.isFinite(5) math.isFinite(bad)
```

Output:

```text
NaN
true
true false
```

`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.

```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", [])
```

Output:

```text
Ada: mean 91.7, spread 6.2
Bo: no scores
```

**Quiz.** What does this print?

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

- [ ] `true`
- [x] `false`
- [ ] A `MathError`

Answer: The square root of -1 is `NaN`, which is never equal to anything, itself included. Test for it with `math.isNaN`.

### Exercise: Mean and range

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

Starting code:

```osl
import "std:math"

number[] temps = [18.5, 21, 19.5, 24, 16]

log temps.len
```

It should print:

```text
19.8
8
```

Hint 1: `math.avg(temps).unwrap()` gives the mean, and `math.toFixed(x, 1)` formats it.

Hint 2: `math.rangeOf` is the largest minus the smallest.

A solution:

```osl
import "std:math"

number[] temps = [18.5, 21, 19.5, 24, 16]

log math.toFixed(math.avg(temps).unwrap(), 1)
log math.rangeOf(temps).unwrap()
```

## API reference

## API reference

Every member of `std:math`, generated from the source of OSL.

### 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.
