Math
import "ghost:math"
import { sqrt, pi } from "ghost:math"
The math module covers the mathematics a program is likely to need: rounding, roots, logarithms, trigonometry, statistics, and linear algebra. Every method here works on a single number, and almost all of them work just as well on a list of numbers, or a list of lists.
The array half of the module — building lists of numbers, reshaping them, and the linear algebra — has its own page: Arrays.
Broadcasting
Most methods are elementwise. Hand one a number and it answers with a number; hand it a list and it answers with a list of the same shape, having applied itself to every element.
math.sqrt(16) // 4
math.sqrt([1, 4, 9]) // [1, 2, 3]
math.sqrt([[1, 4], [9, 16]]) // [[1, 2], [3, 4]]
Where more than one value is involved, shapes are lined up from the right. At each axis the two lengths must match, or one of them must be 1, or one side must have no axis there at all. Whichever it is, the shorter side repeats.
math.add([1, 2, 3], 10) // [11, 12, 13] — the number repeats
math.add(10, [1, 2, 3]) // [11, 12, 13] — either side may be the list
math.multiply([1, 2, 3], [4, 5, 6]) // [4, 10, 18] — paired off
math.clamp([-5, 5, 15], 0, 10) // [0, 5, 10] — bounds repeat
The case worth knowing is a row against a matrix. The row is stretched down every row of the matrix rather than paired against them, which is what makes a single bias row apply to a whole batch:
math.add([[1, 2], [3, 4]], [10, 20]) // [[11, 22], [13, 24]]
math.add([[1, 2], [3, 4]], [[10], [20]]) // [[11, 12], [23, 24]]
Lists have to be rectangular to take part — a ragged list has no shape to line up, and says so rather than guessing.
Whole numbers stay whole wherever the answer is exact, so a result can index a list without a conversion.
math.pow(2, 10) // 1024, a whole number
math.pow(2, 0.5) // 1.4142135623730951
Rounding and sign
| Method | Returns |
|---|---|
math.floor(n) | The largest whole number less than or equal to n. |
math.ceil(n) | The smallest whole number greater than or equal to n. |
math.round(n) | n rounded to the nearest whole number. |
math.round(n, places) | n rounded to places decimal places. |
math.truncate(n) | n with its fraction dropped, toward zero. |
math.abs(n) | The absolute, or non-negative, value. |
math.sign(n) | -1, 0, or 1. |
math.floor(2.7) // 2
math.truncate(-2.7) // -2, where math.floor(-2.7) is -3
math.round(2.567, 2) // 2.57
floor, ceil, round, truncate, and sign all answer with whole numbers, so they can be used as list indices directly.
Powers, roots, and logarithms
| Method | Returns |
|---|---|
math.sqrt(n) | The square root. |
math.cbrt(n) | The cube root. |
math.square(n) | n multiplied by itself. |
math.reciprocal(n) | 1 / n. |
math.pow(base, exponent) | base raised to exponent. |
math.hypot(x, y) | The hypotenuse, without overflowing on the way. |
math.exp(n) | e raised to n. |
math.exp2(n) | 2 raised to n. |
math.expm1(n) | math.exp(n) - 1, accurate for tiny n. |
math.log(n) | The natural logarithm. |
math.log(n, base) | The logarithm in the given base. |
math.log2(n) / math.log10(n) | The logarithm in base 2 or 10. |
math.log1p(n) | math.log(1 + n), accurate for tiny n. |
math.log(8, 2) // 3
math.hypot(3, 4) // 5
Trigonometry
Angles are radians throughout.
| Method | Returns |
|---|---|
math.sin(n) / math.cos(n) / math.tan(n) | Sine, cosine, tangent. |
math.asin(n) / math.acos(n) / math.atan(n) | Their inverses. |
math.atan2(y, x) | The angle to a point, correct in all four quadrants. |
math.sinh(n) / math.cosh(n) / math.tanh(n) | The hyperbolic forms. |
math.asinh(n) / math.acosh(n) / math.atanh(n) | Their inverses. |
math.degrees(radians) | Radians converted to degrees. |
math.radians(degrees) | Degrees converted to radians. |
angle = math.atan2(target.y - player.y, target.x - player.x)
x = math.cos(angle) * speed
y = math.sin(angle) * speed
atan2 is the one to reach for over atan: it takes both coordinates, so it knows which quadrant the point is in.
Arithmetic
Arithmetic on lists is elementwise, and the operators say so directly:
[1, 2, 3] + 10; // [11, 12, 13]
[1, 2, 3] * 2; // [2, 4, 6]
[1, 2, 3] + [10, 20, 30]; // [11, 22, 33]
[[1, 2], [3, 4]] + [10, 20]; // [[11, 22], [13, 24]]
The methods below are the same operations under a name, broadcasting identically — a + b and math.add(a, b) are one operation reached two ways. Reach for whichever reads better: the operators for arithmetic in the middle of an expression, the methods where a name carries more.
See Lists for what the operators mean in full, including comparison and joining.
| Method | Returns |
|---|---|
math.add(a, b) | a + b. |
math.subtract(a, b) | a - b. |
math.multiply(a, b) | a * b. |
math.divide(a, b) | a / b. Dividing by zero is an error. |
math.mod(a, b) | The remainder, taking its sign from a. |
math.remainder(a, b) | The IEEE remainder, taking its sign from the nearer multiple. |
math.copySign(magnitude, sign) | magnitude with the sign of sign. |
math.maximum(a, b) | The larger of the two, elementwise. |
math.minimum(a, b) | The smaller of the two, elementwise. |
prices = [10, 20, 30]
math.multiply(prices, 1.08) // [10.8, 21.6, 32.400000000000006]
math.maximum(prices, 15) // [15, 20, 30]
maximum and minimum compare two things elementwise. max and min are different methods that reduce a whole collection to one value.Comparison and predicates
Each of these answers true or false, and broadcasts like everything else — given a list, you get a list of booleans.
| Method | True when |
|---|---|
math.isZero(n) | n is zero. |
math.isPositive(n) / math.isNegative(n) | n is above or below zero. |
math.isEven(n) / math.isOdd(n) | n is a whole number and even, or odd. |
math.isInteger(n) | n has no fractional part. |
math.isFinite(n) | n is neither infinite nor nan. |
math.isInfinite(n) / math.isNaN(n) | n is infinite, or is nan. |
math.isPrime(n) | n is a prime number. |
math.isClose(a, b) | a and b are equal to within a small tolerance. |
math.isClose(a, b, tolerance) | …to within the tolerance you name. |
isClose is the comparison to use after any run of decimal arithmetic, where == asks a question the arithmetic cannot answer:
0.1 + 0.2 == 0.3 // false
math.isClose(0.1 + 0.2, 0.3) // true
It compares against both an absolute and a relative tolerance, so it holds up for values near zero and for very large ones alike.
Interpolation and bounds
| Method | Returns |
|---|---|
math.clamp(value, low, high) | value, held between low and high. |
math.lerp(from, to, amount) | A value between from and to, at amount from 0 to 1. |
math.smoothstep(low, high, value) | The same, eased in and out, clamped to the edges. |
clamp keeps a value inside a range — a camera inside its map, a health bar between empty and full. lerp is how something follows smoothly rather than snapping:
camera.x = math.lerp(camera.x, target.x, 8 * dt)
camera.x = math.clamp(camera.x, 0, map.width - window.width)
clamp answers with one of the three values it was given, so clamping whole numbers leaves them whole.
Whole-number mathematics
| Method | Returns |
|---|---|
math.gcd(a, b, …) | The greatest common divisor. |
math.lcm(a, b, …) | The least common multiple. |
math.factorial(n) | n!. |
math.combinations(n, k) | How many ways k can be chosen from n, order ignored. |
math.permutations(n, k) | The same, order counted. |
math.combinations(52, 5) // 2598960 — five-card poker hands
math.factorial(20) // 2432902008176640000
These stay exact while the answer fits in a whole number and fall back to decimals beyond that, rather than wrapping around.
Randomness and noise
| Method | Returns |
|---|---|
math.randomInt(n) | A whole number from 1 to n, both ends included. |
math.randomInt(low, high) | A whole number from low to high, both ends included. |
math.randomSeed(n) | Seeds the generator. |
math.noise(x) / math.noise(x, y) | Smooth noise for the given coordinate, between 0 and 1. |
math.randomInt() is the one call that always answers a whole number. For a decimal, use random.random() — the two are named apart precisely because they answer different kinds of number from the same generator.
math.randomSeed() and random.seed() drive that one shared generator, so a single seed governs both modules and a seeded run replays exactly however you ask for your numbers.
noise() is what you want when variation should be smooth rather than jittery — terrain height, cloud cover, a flicker. Neighbouring inputs give neighbouring outputs, which is exactly what randomInt() does not do, and the same input always gives the same result.
math.randomSeed(1234) // the same sequence every run
height = math.noise(x * 0.05) * 40
Special functions
| Method | Returns |
|---|---|
math.gamma(n) | The gamma function, which extends the factorial to decimals. |
math.logGamma(n) | Its natural logarithm, which survives inputs gamma cannot. |
math.erf(n) / math.erfc(n) | The error function and its complement. |
Constants
pi
The mathematical constant π = 3.141592…. Use round() for the precision you want.
math.pi.round(2)
// expected value: 3.14
tau
The mathematical constant τ = 6.283185…, which is 2π — the ratio of a circle's circumference to its radius.
math.tau.round(2)
// expected value: 6.28
e
The mathematical constant e = 2.718281…, the base of the natural logarithm.
math.e.round(2)
// expected value: 2.72
phi
The golden ratio, φ = 1.618033….
sqrt2
The square root of 2, 1.414213….
sqrtPi
The square root of π, 1.772453….
ln2
The natural logarithm of 2, 0.693147….
ln10
The natural logarithm of 10, 2.302585….
log2e
The base-2 logarithm of e, 1.442695….
log10e
The base-10 logarithm of e, 0.434294….
epsilon
Machine epsilon: the gap between 1 and the next decimal above it, 2.22e-16. It is the smallest difference that can still be told apart at that scale, and so the floor on how precise a comparison between decimals near 1 can be.
math.epsilon
// 2.220446049250313e-16
// (printing it writes the value out in full)
For "are these two numbers close enough", reach for math.isClose() rather than comparing against epsilon by hand — it combines an absolute and a relative tolerance, so it holds up near zero and for very large values alike.
smallestNumber
The smallest positive decimal distinguishable from zero, 5e-324.
largestNumber
The largest representable decimal, 1.797693…e+308. Like smallestNumber, printing it writes the value out in full.
smallestInteger and largestInteger
The bounds of a whole number, -9223372036854775808 and 9223372036854775807.
infinity
A value larger than every other. Dividing by zero is an error in Ghost, so this is how you name infinity when an algorithm needs it — as a starting point for a running minimum, say, or as the order argument to math.norm().
nan
"Not a number": the result of an undefined operation, such as the square root of a negative. It compares equal to nothing, itself included, so test for it with math.isNaN().
math.sqrt(-1) == math.nan // false, always
math.isNaN(math.sqrt(-1)) // true