This library provides several useful math utilities as well as pass-through declarations of the standard JavaScript Math library.
These include min-max seeking, zero finding, assistance for integer and modular math, angle calculations, conversions and formatting, and support for working with spherical coordinates.
The chief motivation behind the creation of this library was solving astronomical calculations (as those available via @tubular/astronomy), but the functionality provided here is applicable to many tasks.
npm install @tubular/math
import { cos_deg, interpolate, union,
...} from '@tubular/math'; // ESM
...or...
const { cos_deg, interpolate, union,
...} = require('@tubular/math'); // CommonJS
Documentation examples will assume @tubular/math has been imported as above.
To remotely download the full code as an ES module:
<script type="module">
import('https://unpkg.com/@tubular/math/dist/index.min.mjs').then(pkg => {
const { cos_deg, interpolate, union } = pkg;
// ...
});
</script>
For the old-fashioned UMD approach:
<script src="https://unpkg.com/@tubular/math/dist/index.min.js"></script>
The @tubular/math package will be made available via the global variable tbMath
. Functions, classes, and constants will also be available via this variable, such as tbMath.cos_deg
, tbMath.interpolate
, tbMath.union
, etc.
abs
, acos
, acosh
, asin
, asinh
, atan
, atan2
, atanh
, cbrt
, clz32
, cos
, cosh
, E
, exp
, expm1
, fround
, hypot
, imul
, LN10
, LN2
, log
, log10
, LOG10E
, log1p
, log2
, LOG2E
, max
, min
, PI
, pow
, sign
, sin
, sinh
, sqrt
, SQRT1_2
, SQRT2
, tan
, tanh
, trunc
function acos_deg(x: number): number;
Same as Math.acos
, but returns a result in degrees rather than radians.
function acot(x: number): number;
Arccotangent (cot‑1).
function acot2(y: number, x: number): number;
Two-argument version of arccotangent (cot‑1), conceptually related to atan2
.
function acot_deg(x: number): number;
Arccotangent (cot‑1), but returns a result in degrees rather than radians.
function acot2_deg(y: number, x: number): number;
Two-argument version of arccotangent (cot‑1), conceptually related to atan2
, returning a result in degrees rather than radians.
function asin_deg(x: number): number;
Same as Math.asin
, but returns a result in degrees rather than radians.
function atan_deg(x: number): number;
Same as Math.atan
, but returns a result in degrees rather than radians.
function ceil(x: number, multiple = 1): number;
Same as Math.ceil
when the second argument is omitted (or equals 1), otherwise rounds x
upward to the nearest integer multiple of multiple
.
function convertFromRadians(angle: number, unit: Unit): number
Converts an angle expressed in radians to an angle expressed in unit
, where unit
is one of the following enumerated values:
enum Unit { RADIANS, DEGREES, ARC_MINUTES, ARC_SECONDS, HOURS, HOUR_ANGLE_MINUTES, HOUR_ANGLE_SECONDS, ROTATIONS, GRADS }
function convertToRadians(angle: number, unit: Unit): number
Converts an angle expressed in unit
to an angle expressed in radians, where unit
is one of the above-listed enumerated values, e.g. Unit.ARC_MINUTES
.
function cos_deg(x: number): number;
Same as Math.cos
, but accepts an argument in degrees rather than radians.
function div_tt0(x: number, y: number): number;
Integer division with “truncation toward zero”, that is, positive results of x/y round downward to the nearest integer, and negative results round upward to the nearest integer.
function div_rd(x: number, y: number): number;
Integer division where the result of x/y always rounds downward to the nearest integer.
function floor(x: number, multiple = 1): number;
Same as Math.floor
when the second argument is omitted (or equals 1), otherwise rounds x
downward to the nearest integer multiple of multiple
.
function interpolate(x0: number, x: number, x1: number, y0: number, y1: number): number;
Given two points, (x0, y0), (x1, y1), and a value x such that x0 ≤ x ≤ x1, this function returns a corresponding y value by simple linear interpolation.
function interpolateModular(x0: number, x: number, x1: number, y0: number, y1: number, modulus: number, signedResult = false): number;
This function works identically to the interpolate
function above except that results are pinned within a modular range of [0, modulus) when signedResult
is omitted or false
, or [‑modulus/2, modulus/2) if signedResult
is true.
This is useful for interpolating angular values, not only to pin results to a range such as [0, 360) or [‑180, 180), but when the input values might span across a modular discontinuity.
function interpolateTabular(xx: number[], yy: number[], x: number, maxSpan = 0): number;
This function finds the value of y for a given value of x by tabular interpolation, using (x, y) pairs from xx
and yy
. maxSpan
can typically be omitted (or set to 0).
For special cases where there is a possible discontinuity in the source of the tabular values (e.g. some values come from an historical table of past values, whereas others come from a predicative formula for future values), a non-zero maxSpan
limits the range of tabular values used for the interpolation to tabular value pairs where the x value is in the range [x - maxSpan
, x + maxSpan
].
Note: Using a non-zero maxSpan
imposes the requirement that the xx
array be sorted in ascending order, and the yy
array be sorted so that its values pair correctly with the sorted xx
values.
What the maxSpan
limit achieves, in terms of the example cited, is the creation of three separate types of results: results that are based only on historical values, results which are based only on predicative values, and a transitional range of results where the interpolation smoothly blends the two source ranges using weighted averaging.
function irandom(maxValue: number): number;
function irandom(lowest: number, highest: number): number;
- With one argument, this function returns a random integer in the range [1, maxValue].
- With two arguments, this function returns a random integer in the range [lowest, highest].
function limitNeg1to1(x: number): number;
Returns the value of x pinned within the range [‑1, 1], a function particularly useful for preventing NaN
results from asin
or acos
when the value of x might overflow this range slightly due to rounding errors.
function mod(x: number, y: number): number;
This is equivalent to x % y
except for the function’s behavior with negative argument values. The resulting value tracks the sign of y
, not the sign of x
. When y
is positive, the returned value will be in the range [0, y). When y
is negative, the returned value will be in the range (y, 0]. (This matches the behavior of the Python %
operator.)
function mod2(x: number, y: number): number;
Like the mod
function above, except when y
is positive, the returned value will be in the range [‑y/2, y/2). When y
is negative, the returned value will be in the range (‑y/2, y/2].
function random(): number;
function random(maxValue: number): number;
function random(lowest: number, highest: number): number;
- With no arguments, this function is equivalent to
Math.random
. - With one argument, this function returns a random number in the range [0, maxValue).
- With two arguments, this function returns a random number in the range [lowest, highest).
function round(x: number, multiple = 1): number;
Same as Math.round
when the second argument is omitted (or equals 1), otherwise rounds x
to the nearest integer multiple of multiple
, rounding upward when x is exactly halfway between two multiples.
function signZN(x: number): number;
Like Math.sign
, but returns -1 for an x
of 0.
function signZP(x: number): number;
Like Math.sign
, but returns 1 for an x
of 0.
function sin_deg(x: number): number;
Same as Math.sin
, but accepts an argument in degrees rather than radians.
function squared(x: number): number;
Returns x2.
function tan_deg(x: number): number;
Same as Math.tan
, but accepts an argument in degrees rather than radians.
function to_degree(x: number): number;
Converts x
in radians to degrees.
function to_radian(x: number): number;
Converts x
in degrees to radians.
function union(r1: Rectangle, r2: Rectangle): Rectangle;
Returns the smallest Rectangle
which encloses both r1
and r2
.
interface Point {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
interface Rectangle {
x: number;
y: number;
w: number;
h: number;
}
The Angle
class represents immutable angle values, with methods to facilitate angular calculations and formatting angular values. Angle instances cache sin, cos, and tan values internally so they only need to be computed once.
constructor(angle = 0, unit?: Unit, mode = Mode.RANGE_LIMIT_SIGNED)
- With no arguments,
new Angle()
creates a zero-valued angle, equivalent to the constant Angle.ZERO. - With one argument an
Angle
instance ofangle
radians is created. - With two arguments an
Angle
instance ofangle
expressed inunit
is created. As a radian value,angle
will be adjusted to the range [-π, π), or [-180, 180) as degrees, etc. - Given three arguments, and when
mode
isMode.RANGE_LIMIT_SIGNED
, a newAngle
instance is created as described above.
Amode
ofMode.RANGE_LIMIT_NONNEGATIVE
coercesangle
into the range [0, 2 π) in radians, [0, 360) in degrees, etc.
Amode
ofMode.RANGE_UNLIMITED
leaves the value ofangle
as-is.
Angle.asin
, Angle.asin_nonneg
, Angle.acos
, Angle.atan
, Angle.atan_nonneg
, Angle.atan2
, and Angle.atan2_nonneg
all function the same as the like-named general functions, but returning an Angle
instance equivalent to the expected value in radians.
Angle.parse(s: string, throwException = false): Angle
This method parses a string, either in degrees or hours, and an Angle instance. If the string can’t be parsed as a valid value, the method either returns null
, or if throwException
is specified and true
, throws an "Invalid angle" exception.
If the characters h
or m
, are found within the string, the angle is treated as an hour angle, otherwise the angle is considered to be in degrees. A leading -
will negate the angle value, or, when parsing degree values, a trailing e
or s
will negate the value. (These rules are case-insensitive.)
No range-limiting is applied — "700"
will parse as an angle of 700°, not 340° or -20°.
enum Unit { RADIANS, DEGREES, ARC_MINUTES, ARC_SECONDS,
HOURS, HOUR_ANGLE_MINUTES, HOUR_ANGLE_SECONDS,
ROTATIONS, GRADS }
enum Mode { RANGE_LIMIT_SIGNED, RANGE_LIMIT_NONNEGATIVE,
RANGE_UNLIMITED }
arcMinutes
, arcSeconds
, degrees
, grads
, hours
, radians
, rotations
For example, given:
const a = new Angle(60, Unit.DEGREES);
...a.hours
has a value of 4, a.arcMinutes
is 3600, and a.rotations
is 0.16666666666666666.
You can also access different unit values via the function:
getAngle(unit = Unit.RADIANS): number
...such that a.getAngle(Unit.HOURS)
would return 4, a.getAngle(Unit.ARC_MINUTES)
would return 3600, etc.
cos
, sin
, tan
Given a
as defined in the previous examples, a.cos
returns 0.5 (well, okay, 0.5000000000000001 because rounding isn’t perfect). Note: No parenthesis!
add(angle2: Angle, mode = Mode.RANGE_LIMIT_SIGNED): Angle;
subtract(angle2: Angle, mode = Mode.RANGE_LIMIT_SIGNED): Angle;
complement(mode = Mode.RANGE_LIMIT_SIGNED): Angle;
supplement(mode = Mode.RANGE_LIMIT_SIGNED): Angle;
opposite(mode = Mode.RANGE_LIMIT_SIGNED): Angle;
negate(mode = Mode.RANGE_LIMIT_SIGNED): Angle;
multiply(x: number, mode = Mode.RANGE_LIMIT_SIGNED): Angle;
divide(x: number, mode = Mode.RANGE_LIMIT_SIGNED): Angle;
Omitting the default mode
argument of Mode.RANGE_LIMIT_SIGNED
for clarity:
add(angle2: Angle): Angle;
subtract(angle2: Angle): Angle;
complement(): Angle;
supplement(): Angle;
opposite(): Angle;
negate(): Angle;
multiply(x: number): Angle;
divide(x: number): Angle;
For non-negative results, without the need to specify Mode.RANGE_LIMIT_NONNEGATIVE
:
add_nonneg(angle2: Angle): Angle;
subtract_nonneg(angle2: Angle): Angle;
complement_nonneg(): Angle;
supplement_nonneg(): Angle;
opposite_nonneg(): Angle;
negate_nonneg(): Angle; // Sounds contradictory perhaps, but `negate_nonneg` of 20° is simply 340°.
multiply_nonneg(x: number): Angle;
divide_nonneg(x: number): Angle;
toString(): string
With no arguments, the default string conversion is to display an angle value in decimal degrees with three digits of precision, followed by a degree (°
) symbol.
toString(format?: number, precision?: number): string
Stringifies an angle as decimal degrees according to format
, specified using the following constants:
const FMT_DD = 0x01; // Integer degrees zero-padded to two digits const FMT_HH = 0x01; // Integer hours zero-padded to two digits (for use with // toHourString and toTimeString) const FMT_DDD = 0x02; // Integer degrees zero-padded to three digits const FMT_MINS = 0x04; // Display arcminutes const FMT_SECS = 0x08; // Display arcseconds (and arcminutes too) const FMT_SIGNED = 0x10; // Prefix positive values with `+` (`-` appears when needed)
These constants can be combined using |
to express combinations of formatting options, such as FMT_DD | FMT_MINS | FMT_SIGNED
to format an angle as a signed angle, integer portion zero-padded to two digits, with minute resolution, e.g. 3.5° becomes +03°30'
.
The optional precision
parameter (defaulting to 0) specifies how many decimal places to display as part of the smallest unit or subunit. For example:
new Angle(3.5, Unit.DEGREES).toString(null, 2)
➜ 3.50°
new Angle(3.5, Unit.DEGREES).toString(FMT_MINS)
➜ 3°30'
new Angle(3.5, Unit.DEGREES).toString(FMT_MINS, 2)
➜ 3°30.00'
toSuffixedString(positiveSuffix: string, negativeSuffix: string,
format?: number, precision?: number): string
This works like toString()
, but rather than indicating the sign of the angle value using a leading +
or -
sign, either positiveSuffix
or negativeSuffix
is added at the end of the string representation. Typical values of positiveSuffix
would be N
or W
for North or West. Typical values of negativeSuffix
would be S
or E
for South or East.
toHourString(format?: number, precision?: number): string
Formats an angle as an hour angle (24 hours = 360°), using h
, m
and s
to denote hours, minutes, and seconds, e.g. 12h34m56s
.
toTimeString(format?: number, precision?: number): string
Formats an angle as an hour angle (24 hours = 360°), using colons (:
) to separate hours, minutes, and seconds, e.g. 12:34:56
.
Instances of this class are immutable objects containing two Angle
values, representing longitude and latitude (or right ascension and declination, or azimuth and altitude).
constructor(longitude: Angle | number = 0, latitude: Angle | number = 0,
longUnit = Unit.RADIANS, latUnit?: Unit)
- With no arguments an instance equivalent to 0°N, 0°W is created.
- With two arguments an instance of
longitude
,latitude
is created. Numerical arguments will be interpreted as radians. - With three arguments an instance of
longitude
,latitude
is created, numerical arguments both interpreted according tolongUnit
. - With four arguments an instance of
longitude
,latitude
is created, numerical arguments interpreted according tolongUnit
andlatUnit
, respectively.
altitude
, azimuth
, declination
, latitude
, longitude
, rightAscension
azimuth
and rightAscension
are equivalent to longitude
.
altitude
and declination
are equivalent to latitude
.
distanceFrom(p: SphericalPosition): Angle
This method computes the angular distance between a SphericalPosition
instance and another SphericalPosition
instance.
Instances of this class are a subclass of SphericalPosition
with the addition of a radius value, specifying a unique point in a 3D space.
constructor(longitude?: Angle | number, latitude?: Angle | number,
radius = 0, longUnit?: Unit, latUnit?: Unit)
This works the same way as the SphericalPosition
constructor, with the addition of a radius
argument.
SphericalPosition3D.convertRectangular(x: number, y: number, z: number): SphericalPosition3D
SphericalPosition3D.convertRectangular(point: Point3D): SphericalPosition3D
These methods convert rectangle coordinates to 3D spherical coordinates.
from2D(pos: SphericalPosition, radius: number): SphericalPosition3D
This method creates a SphericalPosition3D
instance from a SphericalPosition
instance and a radius
value.
radius
: The radius value.
xyz
: The rectangular coordinates for the SphericalPosition3D
instance.
This class finds estimated minima or maxima of numerical functions by parabolic interpolation, using Brent’s Method.
constructor(minMaxSeekingFunction: (x: number) => number,
tolerance: number, maxIterations: number,
xa: number, xb: number, xc: number)
This creates an instance of MinMaxFinder
to find the x value for which the value minMaxSeekingFunction(x)
reaches a minimum (or maximum) value over the range xa ≤ x ≤ xc, using the value xb (also in the range xa ≤ xb ≤ xc) as a hint for whether a minimum or maximum value should be sought.
Seeking is an iterative process. Once the difference in estimates between successive iterations is less than or equal to tolerance
an estimate with be returned.
Often very accurate estimates can be found in less than 10 iterations, but maxIterations
puts a limit on how many iterations will be attempted before possibly giving up, as some functions cannot be guaranteed to produce a solution.
getXAtMinMax(): number
Returns the estimated value of x at the minimum (or maximum) point of the function minMaxSeekingFunction
in the range xa ≤ x ≤ xc within tolerance
of the best possible estimate, or the closest approximation reached when maxIterations
have been exhausted.
Note: using any accessor will invoke getXAtMinMax()
if it has not already been invoked.
foundMaximum
true
if a maximum value rather than a minimum value was found. true
does not signify, however, that the result is a valid result found within tolerance
or maxIterations
.
foundMinimum
true
if a minimum value rather than a maximum value was found. true
does not signify, however, that the result is a valid result found within tolerance
or maxIterations
.
lastY
The last y value computed when either a solution was found or maxIterations
were exhausted.
iterationCount
The total number of iterations needed either to solve for the min/max value, or when maxIterations
was reached.
resolved
true
if a valid estimate was found within tolerance
before maxIterations
were exhausted.
This class finds estimated zero-axis crossing/contact points of functions, i.e. x values at which f(x) momentarily have a value of 0.
constructor(zeroSeekingFunction: (x: number) => number, tolerance: number,
maxIterations: number, x1: number, x2: number, maxError?: number)
This creates an instance of ZeroFinder
to find the estimated x value at which the value zeroSeekingFunction(x)
becomes zero, over the range x1 ≤ x ≤ x2.
Like MinMaxFinder
, a solution is found using an iterative process. Once the difference in estimates between successive iterations is less than or equal to tolerance
an estimate with be returned.
If a maxError
value is provided, and the absolute value of the closest-to-zero result found after maxIterations
is greater than maxError
, getXAtZero()
will return NaN
.
constructor(zeroSeekingFunction: (x: number) => number, tolerance: number,
maxIterations: number, x1: number, y1: number, x2: number, y2: number, maxError?: number)
This constructor works the same as above except that the values y1 and y2 corresponding to x1 and x2 are provided precomputed.
getXAtZero(): number
Returns the estimated value of x in the range x1 ≤ x ≤ x2 for which the function zeroSeekingFunction(x)
reaches 0, within tolerance
of the best possible estimate, or the closest approximation reached when maxIterations
have been exhausted.
The value of tolerance
applies not only to the difference between successive estimates of x, but to how close zeroSeekingFunction(x)
is to 0.
maxError
applies only to the closest-to-zero zeroSeekingFunction(x)
value found, resulting in getXAtZero()
returning NaN
if the absolute value of that closest value exceeds maxError
.
Note: using any accessor will invoke getXAtZero()
if it has not already been invoked.
lastY
The last y value computed when either a solution was found or maxIterations
were exhausted.
iterationCount
The total number of iterations needed either to solve for the min/max value, or when maxIterations
was reached.
resolved
true
if a valid estimate was found within tolerance
before maxIterations
were exhausted.