Math object

Math supplies rounding, powers, roots, trigonometry, extrema, and pseudorandom values; explicit numeric contracts prevent precision and security bugs.

level beginner time 12 min at Standard depth
version Node 24
what

Math is a non-constructible built-in object whose static properties and methods perform common mathematical operations. It works with JavaScript Number values, not arbitrary-precision decimals or BigInt.

trap

Rounding direction, floating-point error, NaN propagation, and random interval endpoints can all hide behind plausible formulas. Math.random() is also unsuitable for tokens, verification codes, or other security uses.

fix

Specify the numeric domain, interval, and error budget before choosing a method; test the boundaries, and use Web Crypto for security-sensitive randomness.

What it is and why it exists

Math is a built-in JavaScript object that groups constants such as pi with common numeric algorithms. You call Math.sqrt(9) or read Math.PI directly; you don’t and can’t create instances with new Math(). It isn’t a function, and it has no set of methods for instances to inherit.

The object solves common Number operations, not every mathematical problem. You meet it when finding absolute values and extrema, rounding, computing powers and roots, using trigonometry, or generating ordinary pseudorandom values. Addition, subtraction, multiplication, division, and remainder remain language operators.

Math methods accept and return JavaScript numeric values. The difficult part in most application code isn’t the method name but the input contract: whether values must be finite, whether negatives are valid, which endpoints are included, whether decimal exactness matters, and whether randomness is security-sensitive.

Math doesn’t supply vectors, matrices, complex numbers, statistical distributions, or arbitrary-precision decimal types. When requirements exceed scalar operations, define the data model and precision contract before choosing a dedicated implementation. Don’t present a growing bag of helpers as capabilities of Math itself.

How it works

All Math properties are static members. Constants include Math.PI and Math.E; methods fall into groups for rounding, sign and magnitude, powers, roots and logarithms, trigonometry, extrema and distance, and randomness.

PurposeCommon membersKey contract
Roundingfloor, ceil, trunc, roundDirections differ; negative inputs expose the difference
Powers and rootssqrt, cbrt, pow, exp, logOut-of-domain inputs usually produce NaN or an infinity
Trigonometrysin, cos, tan, atan2Angles use radians, not degrees
Aggregationmin, max, hypotmin and max take separate arguments, not an array
RandomnessrandomReturns a value in a half-open interval from 0 to 1 and accepts no seed

Rounding directions

The four common rounding methods aren’t synonyms. Math.floor(x) rounds toward negative infinity, Math.ceil(x) toward positive infinity, and Math.trunc(x) toward zero. Math.round(x) selects the nearest integer; an exact midpoint goes toward positive infinity, so Math.round(-2.5) is -2, not a banker’s-rounding alternation between -2 and -3.

Inputfloorceiltruncround
2.72323
-2.7-3-2-2-3
-2.5-3-2-2-2

Math.round(-0.5) also produces negative zero. Negative zero normally displays as 0, but Object.is(value, -0) distinguishes it, and 1 / -0 produces -Infinity. When the direction of zero carries meaning, don’t rely on formatted output alone.

Despite “round” in its name, Math.fround() rounds a value to the nearest 32-bit floating-point representation; it doesn’t round to a number of decimal places. Methods such as Math.clz32() are also 32-bit numeric tools, not substitutes for general integer validation.

Argument conversion and special values

Most Math methods convert arguments to Number, so Math.abs('-3') returns 3, and an empty string may become 0. That convenience isn’t reliable input validation. At an API boundary, reject disallowed types first, then use Number.isFinite(), Number.isInteger(), or Number.isSafeInteger() to enforce the required domain.

You can’t pass BigInt to a Math method that requires Number. Mechanically applying Number(bigint) to make the call pass can lose integer precision first. Keep BigInt operators for integer-only work; when conversion is genuinely required, prove the value is in the safe range.

Special values propagate through calculations. Math.sqrt(-1) is NaN, Math.log(0) is -Infinity, and one NaN argument makes both Math.max() and Math.min() return NaN. These results usually don’t throw immediately, so validate them at the source or system boundary.

Empty argument lists have defined results: Math.max() returns -Infinity, and Math.min() returns Infinity. Those values are useful mathematical identity bounds but may not match the business meaning of “no data.” The calling contract must decide whether an empty collection returns null, throws, or uses a default.

Floating-point results

JavaScript Number uses binary floating-point . Many decimal fractions have no exact finite binary representation, so 0.1 + 0.2 isn’t the same floating-point value as the source literal 0.3. Trigonometric functions, logarithms, and roots can also leave last-digit errors.

Number.EPSILON is the gap between 1 and the next larger representable value, not a global tolerance for every scale. When comparing approximate results, derive a tolerance from the business error budget and decide whether absolute error, relative error, or both fit the magnitude. Near-zero comparisons particularly need an absolute tolerance.

The specification permits implementation approximations for some transcendental functions. Conforming engines can differ in their last few digits, so cross-runtime tests shouldn’t assert meaningless full decimal expansions. When a business rule requires portable, exact decimal results, Math plus binary Number isn’t a complete solution.

Random-number intervals

Math.random() returns an approximately uniform pseudorandom Number greater than or equal to 0 and less than 1. This half-open interval is written [0, 1): the left endpoint can occur, while the right endpoint can’t. Any integer-mapping formula must also state whether its target upper bound is included.

The JavaScript API provides no way to seed Math.random(), and the implementation chooses the algorithm. It fits visual jitter, ordinary sampling, and non-security game logic, but not reproducible experiments or security credentials. When tests need a fixed sequence, inject the random source as a dependency.

Security tokens, verification codes, and unpredictable identifiers need cryptographically secure randomness . Browsers and Node 24 expose Web Crypto’s crypto.getRandomValues(). Uniformly mapping random bytes to an arbitrary interval still requires handling modulo bias, so prefer a reviewed high-level API or rejection sampling.

Define the numeric contract first

Before selecting a method, rewrite the natural-language requirement as a testable numeric contract. “Clamp a percentage” must at least say whether strings are accepted, whether the valid interval is [0, 100] or [0, 1], and whether NaN throws or propagates. “Pick a random index” must state what happens for an empty array and whether the upper bound can be chosen.

A minimal contract normally covers these points:

  1. Whether input is a Number, BigInt, or convertible text.
  2. Whether the unit is radians, degrees, pixels, seconds, or a minor currency unit.
  3. Whether each interval endpoint is included and whether an empty interval is legal.
  4. Whether absolute error, relative error, or exact decimal behavior is required.
  5. How to handle NaN, infinities, negative zero, and empty collections.
  6. Whether random output needs ordinary distribution, reproducibility, or resistance to prediction.

A method name can’t make these decisions. Math.max() doesn’t know that an empty array means “no observations,” Math.round() doesn’t know an invoice’s midpoint rule, and Math.random() doesn’t know whether its output becomes an identity credential.

Keep validation near system boundaries and semantic conversions. If a function converts degrees to radians, validate the degree range before conversion and put the radians in a newly named variable. If it clamps a measurement, first decide whether invalid input should be rejected or clamped so NaN can’t masquerade as an ordinary boundary value.

Examples

These four examples progress through rounding, coordinate calculation, approximate comparison, and a testable random integer. Every output comes from running the corresponding file locally with Node 24.

Compare rounding directions

Start with a positive value, a negative value, and a midpoint. Object.is() checks negative zero separately so the console can’t disguise it as ordinary zero.

rounding.js
const values = [2.7, -2.7, -2.5];

for (const value of values) {
  console.log(
    `${value}: floor=${Math.floor(value)}, ceil=${Math.ceil(value)}, ` +
      `trunc=${Math.trunc(value)}, round=${Math.round(value)}`,
  );
}

console.log('round(-0.5) is negative zero:', Object.is(Math.round(-0.5), -0));
console.log('hypot(3, 4):', Math.hypot(3, 4));
console.log('max(7, 12, 4):', Math.max(7, 12, 4));
2.7: floor=2, ceil=3, trunc=2, round=3
-2.7: floor=-3, ceil=-2, trunc=-2, round=-3
-2.5: floor=-3, ceil=-2, trunc=-2, round=-2
round(-0.5) is negative zero: true
hypot(3, 4): 5
max(7, 12, 4): 12

Math.hypot(3, 4) directly computes a Euclidean length and returns 5. Math.max() receives three separate arguments. The example groups different categories to show that all of them return ordinary Number values rather than creating mathematical objects.

Clamp coordinates and compute direction

This helper validates finite values and ordered bounds before composing min and max to clamp a range. Math.atan2(y, x) returns radians; conversion happens only when producing degrees for display.

marker-position.js
function clamp(value, minimum, maximum) {
  if (![value, minimum, maximum].every(Number.isFinite) || minimum > maximum) {
    throw new RangeError('expected finite values and minimum <= maximum');
  }
  return Math.max(minimum, Math.min(maximum, value));
}

function placeMarker(point, viewport) {
  const x = clamp(point.x, 0, viewport.width);
  const y = clamp(point.y, 0, viewport.height);
  const angle = Math.atan2(point.y, point.x);

  return {
    x,
    y,
    distanceFromOrigin: Math.hypot(point.x, point.y),
    angleDegrees: angle * 180 / Math.PI,
  };
}

console.log(placeMarker({ x: 300, y: 400 }, { width: 280, height: 450 }));
{
  x: 280,
  y: 400,
  distanceFromOrigin: 500,
  angleDegrees: 53.13010235415598
}

The source point exceeds the viewport width, so the returned x is clamped to 280. Distance and direction still use the source point as part of this function’s contract. If they should use the clamped position instead, pass x and y to hypot and atan2.

Compare against an error budget

The approximate comparison handles exact equality and non-finite values first, then combines an absolute tolerance with a relative tolerance that grows with the scale. The defaults are only an example policy; a real system derives thresholds from measurement resolution, algorithmic error, and units.

nearly-equal.js
function nearlyEqual(
  left,
  right,
  { relativeTolerance = 1e-12, absoluteTolerance = Number.EPSILON } = {},
) {
  if (!Number.isFinite(left) || !Number.isFinite(right)) return left === right;
  if (left === right) return true;

  const difference = Math.abs(left - right);
  const scale = Math.max(Math.abs(left), Math.abs(right));
  return difference <= Math.max(absoluteTolerance, relativeTolerance * scale);
}

console.log('0.1 + 0.2 === 0.3:', 0.1 + 0.2 === 0.3);
console.log('nearly equal:', nearlyEqual(0.1 + 0.2, 0.3));
console.log('large values:', nearlyEqual(1_000_000_000_000, 1_000_000_000_000.5));
console.log(
  'near zero:',
  nearlyEqual(1e-15, 0, { absoluteTolerance: 1e-14 }),
);
0.1 + 0.2 === 0.3: false
nearly equal: true
large values: true
near zero: true

Outside finite inputs, this function accepts only strict equality, so two Infinity values compare equal while NaN doesn’t equal itself. Accepting infinities is also a contract choice; measurement data will usually reject them before calling the comparison helper.

Inject a random source

For an integer in a closed interval, the span is maximum - minimum + 1. The example validates safe-integer bounds and their order, then injects a fixed random sequence so boundary behavior is repeatable.

random-integer.js
function randomIntInclusive(minimum, maximum, random = Math.random) {
  if (!Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximum)) {
    throw new TypeError('bounds must be safe integers');
  }
  if (minimum > maximum) {
    throw new RangeError('minimum must not exceed maximum');
  }

  const span = maximum - minimum + 1;
  if (!Number.isSafeInteger(span)) {
    throw new RangeError('range is too wide');
  }
  return minimum + Math.floor(random() * span);
}

const samples = [0, 0.49, 0.999999];
let index = 0;
const replay = () => samples[index++];

console.log(randomIntInclusive(1, 6, replay));
console.log(randomIntInclusive(1, 6, replay));
console.log(randomIntInclusive(1, 6, replay));
1
3
6

Injection solves testability only. It doesn’t turn Math.random() into a secure source or guarantee perfectly unbiased results over extremely wide intervals. Production calls that use the default still inherit every Math.random() limitation; security-sensitive callers need a cryptographic API.

Pitfalls

Treating Math.round() as decimal or financial rounding

Fix: define the rounding mode and data representation first. Monetary values can use safe integers in minor units when the domain permits it; strict decimal semantics need a verified decimal implementation. toFixed(2) is useful for a display string, but formatting isn’t a storage precision model.

Comparing every result with one fixed Number.EPSILON

Fix: derive absolute and relative tolerances from the domain error budget, then test zero, large values, and both sides of a threshold. Continue to use strict equality for discrete counts that must match exactly; don’t convert every comparison into an approximate one.

Confusing negative rounding and midpoint rules

Fix: name the required direction, then use floor, ceil, trunc, or an explicit rounding algorithm. Test positive and negative fractions, midpoints, negative zero, values beyond the 32-bit range, and NaN. Don’t substitute bitwise operators because of unmeasured microbenchmark folklore.

Spreading a large array into an extrema call

Fix: spread a small, validated collection when appropriate. For unbounded sizes, aggregate item by item with a loop or a reduce with an explicit initial value, and decide how empty collections and invalid numbers behave before aggregation.

Using Math.random() for security or replay

Fix: use Web Crypto or a platform’s secure high-level API for security, and verify that interval mapping is unbiased. For simulation and tests, inject an explicit pseudorandom generator and record its seed. Don’t globally replace Math.random() and alter unrelated code.

Ignoring units, numeric domains, and NaN

Fix: encode units in names such as angleRadians and angleDegrees, and validate finite values and allowed ranges at boundaries. At every step that can produce NaN or an infinity, decide whether to reject, clamp, or explicitly propagate it.

Deep Floating-point boundaries and error budgets

Floating-point boundaries and error budgets

Binary floating-point encodes a finite number with a sign, significand, and binary exponent. JavaScript Number corresponds to IEEE 754 double precision, but that doesn’t make every decimal exact. Integers are individually representable only throughout the safe-integer range; above Number.MAX_SAFE_INTEGER, adjacent mathematical integers can map to the same Number.

Separate at least representation error, algorithmic approximation, and input noise. The representation error in 0.1 comes from decimal-to-binary conversion, Math.sin() also involves function approximation, and a sensor reading can already contain measurement error. Covering all three with an arbitrary 1e-10 makes a comparison unexplained and hard to maintain.

Relative tolerance grows with scale and suits results away from zero at different magnitudes; absolute tolerance provides a fixed error band near zero. A common policy accepts |a - b| <= max(absTol, relTol * max(|a|, |b|)), but the domain still supplies the tolerance values. A business threshold must also say which side includes equality.

The stage at which rounding occurs matters too. Rounding every step can accumulate bias, while rounding only for final display can let intermediate values exceed an allowed business precision. Finance, billing, and regulated calculations need domain rules for representation, rounding points, and midpoint mode instead of a guess based on Math.round().

Negative zero and non-finite values

Negative zero preserves an approach direction or the result of a sign operation. Math.sign(-0) remains -0, and Math.min(0, -0) selects -0, although string formatting normally hides the sign. Preserve it only when direction affects a reciprocal, coordinate transform, or protocol; otherwise, a system boundary can normalize it to ordinary zero.

NaN says that a numeric operation failed to produce a usable number, but it doesn’t explain the reason. Infinity can come from division by zero, overflow, or a defined function boundary. When the cause matters, validate before the Math call and throw an error with context instead of waiting to see only NaN downstream.

Aggregation and numerical stability

Math.hypot(...values) scales intermediate values while computing the square root of a sum of squares. It therefore avoids some premature overflow and underflow that a handwritten Math.sqrt(x * x + y * y) encounters more easily. It still returns a Number and doesn’t validate consistent units; latitude, pixels, and metres don’t become compatible because one method accepts all three.

The empty-argument results of Math.max() and Math.min() are mathematical identity bounds. Business aggregation often needs another meaning, such as null for no data or an error for missing observations. Define the empty-collection result before selecting an initial value so a legitimate Infinity doesn’t leak silently into JSON or a database operation.

Processing a large collection item by item avoids the call-argument limit and provides one place to validate values, record invalid entries, and stop early. Whether you need compensated summation or another stable algorithm depends on the error budget and data distribution. Without requirements and measurements, don’t claim that a micro-optimization is universally faster or more accurate.

Boundaries of powers, logarithms, and angles

Math.sqrt(x) returns only the principal square root. A negative finite real has no real square root, so the result is NaN. Math.cbrt(x) can handle negatives because a negative number has a real cube root. Work in the complex domain needs a different data type and algorithm.

Math.pow(base, exponent) and base ** exponent express the same exponentiation for ordinary numeric values, but operator syntax has its own precedence restriction. In particular, you can’t write -2 ** 2 directly. Write (-2) ** 2 for a negative base or -(2 ** 2) to negate the result of exponentiation.

Math.log(x) is the natural logarithm, not base ten. Common bases have separate Math.log10() and Math.log2() methods. Enforce the logarithm’s domain explicitly too: a negative input produces NaN, while zero produces -Infinity, and those cases usually represent different input failures.

Trigonometric methods consistently take radians, and Math.atan2(y, x) also defines an argument order. Swapping x and y produces a plausible but wrong direction. Name conversion factors and test axes, quadrants, and the zero vector; one 45-degree test is less likely to expose the error.

Inverse trigonometric methods have domains as well. A cosine computed with floating-point arithmetic can drift just outside [-1, 1]; after proving that both vectors are nonzero, geometry code may clamp it back based on that algorithmic invariant. Don’t clamp arbitrary invalid input first, because that conceals actual data errors.

ExpressionResultContract to confirm
Math.sqrt(-1)NaNWhether only the real domain is allowed
Math.log(0)-InfinityWhether zero is a boundary or invalid input
Math.acos(1.0000000000000002)NaNWhether the excess is rounding error or bad data
Math.atan2(0, 0)0Whether the zero vector has a defined direction

The table records language behavior, not automatic business answers. The zero vector has no natural direction even though Math.atan2(0, 0) returns a usable Number. The caller still has to enforce domain invariants before entering a general mathematical function.

Three randomness contracts

Ordinary visual randomness, reproducible experiments, and security randomness are three different contracts. Math.random() covers only the first. Reproducible experiments require an explicit algorithm and seed; security randomness requires an entropy source and reviewed derivation that resist prediction.

Recording a seed alone doesn’t guarantee long-term replay. If the pseudorandom algorithm or sampling steps change, the same seed can produce another sequence. A replayable simulation should also record the algorithm identifier, version, seed, and input order.

The common multiplication formula that maps [0, 1) to [min, max] fits ordinary sampling over a modest span. A floating-point source has finite state and finitely many representable outputs, so you can’t assume perfect equality of chances across a very wide target range. When fairness is auditable, specify the algorithm, input entropy, interval mapping, and records.

Applying % span directly to a random unsigned integer can also introduce modulo bias because the source space size may not be divisible by span. Rejection sampling discards the tail that can’t be partitioned evenly, then reduces accepted values. This is easy security code to get wrong, so prefer a trusted platform API that directly generates the required range.

Test random logic with deterministic samples near the minimum, around the middle, and near the upper endpoint. Statistical tests can detect obvious bias but can’t prove cryptographic security. Security follows from the primitive, entropy source, threat model, and implementation review, not from a histogram that looks uniform.

Further reading

checkpoint

4 questions · 1 predict-the-output · 1 spot-the-bug

Copy as Markdown Interview bank Edit on GitHub Report an error Was this clear?