Skip to main content

Guide

math.js vs numeric.js vs stdlib: Math Library 2026

Compare math.js, numeric.js, and @stdlib/stdlib for mathematical and scientific computing in JavaScript. Matrix operations, statistics, and symbolic math.

·PkgPulse Team·
0

TL;DR

math.js is the most popular and comprehensive math library for JavaScript — supports symbolic math, complex numbers, matrices, units, and a full expression parser. numeric.js focuses on numerical analysis and linear algebra — matrix operations, SVD, eigenvalues. @stdlib/stdlib is a full scientific computing library modeled after NumPy/SciPy — modular, tree-shakable, and growing fast. For a versatile all-around math library: math.js. For numerical linear algebra: numeric.js or mathjs matrices. For NumPy-like scientific computing with statistics: @stdlib/stdlib.

Key Takeaways

  • math.js: ~4M weekly downloads — expression parser, matrices, complex numbers, units, bignumber support
  • numeric.js: ~700K weekly downloads — focused on numerical methods, matrix decompositions
  • @stdlib/stdlib: ~5M weekly downloads (modular packages) — NumPy-like, statistics, distributions
  • math.js can be used both as a library and as an expression evaluator (parsing math strings)
  • @stdlib is tree-shakable — install individual packages (@stdlib/math-base-special-gamma)
  • For most visualization/chart libraries: math.js handles common statistical needs

math.js

math.js — the all-around JavaScript math library:

Basic arithmetic and algebra

import { create, all } from "mathjs"

const math = create(all)

// Basic operations:
math.add(2, 3)         // 5
math.subtract(10, 4)   // 6
math.multiply(3, 4)    // 12
math.divide(15, 4)     // 3.75

// Advanced math:
math.sqrt(144)         // 12
math.pow(2, 10)        // 1024
math.log(math.E)       // 1
math.log(100, 10)      // 2 (log base 10)
math.sin(math.pi / 2)  // 1
math.factorial(10)     // 3628800

// Constants:
math.pi        // 3.141592653589793
math.e         // 2.718281828459045
math.phi       // 1.618033988749895 (golden ratio)
math.i         // Complex number i

Expression parser

import { evaluate, compile } from "mathjs"

// Parse and evaluate math strings (useful for calculators, formulas):
evaluate("2 + 3")                    // 5
evaluate("sqrt(144)")                // 12
evaluate("sin(pi/2)")                // 1
evaluate("log(e)")                   // 1

// With variables:
evaluate("x^2 + y^2", { x: 3, y: 4 })  // 25 (Pythagorean: 9 + 16)

// Compile for repeated evaluation (faster):
const expr = compile("a * b + c")

expr.evaluate({ a: 2, b: 3, c: 4 })  // 10
expr.evaluate({ a: 5, b: 5, c: 0 })  // 25

// Useful for user-defined formulas in apps:
function calculateCustomMetric(formula: string, data: Record<string, number>) {
  try {
    return evaluate(formula, data)
  } catch {
    throw new Error(`Invalid formula: ${formula}`)
  }
}

// User enters: "downloads * 0.5 + stars * 0.3 + score * 0.2"
calculateCustomMetric(
  "downloads * 0.5 + stars * 0.3 + score * 0.2",
  { downloads: 45, stars: 200, score: 95 }
)
// 22.5 + 60 + 19 = 101.5

Matrices

import { matrix, multiply, transpose, inv, det, eigs } from "mathjs"

// Create matrices:
const A = matrix([[1, 2], [3, 4]])
const B = matrix([[5, 6], [7, 8]])

// Operations:
multiply(A, B)     // [[19, 22], [43, 50]]
transpose(A)       // [[1, 3], [2, 4]]
inv(A)             // Inverse: [[-2, 1], [1.5, -0.5]]
det(A)             // Determinant: -2

// Eigenvalues/eigenvectors:
const { values, vectors } = eigs(A)
// values: [-0.37228, 5.37228]

// Solving linear systems Ax = b:
import { lusolve } from "mathjs"

const coefficients = [[2, 1], [1, 3]]
const constants = [8, 13]
const solution = lusolve(coefficients, constants)
// [[3], [2]] → x=3, y=2 for: 2x + y = 8, x + 3y = 13

Statistics

import { mean, median, std, variance, min, max, quantileSeq } from "mathjs"

const data = [4, 8, 6, 5, 3, 2, 8, 9, 2, 5]

mean(data)       // 5.2
median(data)     // 5 (sorted: 2,2,3,4,5,5,6,8,8,9 → median = (5+5)/2)
std(data)        // 2.44... (standard deviation)
variance(data)   // 5.955... (variance)
min(data)        // 2
max(data)        // 9

// Percentiles:
quantileSeq(data, 0.25)  // 25th percentile (Q1)
quantileSeq(data, 0.75)  // 75th percentile (Q3)

Units and complex numbers

import { unit, complex, add, abs, arg } from "mathjs"

// Physical units:
const speed = unit("100 km/h")
speed.toNumber("m/s")              // 27.78
unit("9.8 m/s^2").to("km/h^2")    // Unit conversion

// Complex numbers:
const z1 = complex(3, 4)           // 3 + 4i
const z2 = complex(1, 2)           // 1 + 2i

add(z1, z2)                         // 4 + 6i
abs(z1)                             // 5 (magnitude: √(3²+4²))
arg(z1)                             // 0.9273 radians (angle)

numeric.js

numeric.js — focused on numerical linear algebra:

Matrix operations and decompositions

import numeric from "numeric"

// Matrix creation:
const A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
const b = [1, 2, 3]

// Basic operations:
numeric.add(A, A)              // Element-wise addition
numeric.mul(A, 2)              // Scalar multiplication
numeric.dot(A, b)              // Matrix-vector product
numeric.transpose(A)           // Transpose

// Solve Ax = b (linear system):
const x = numeric.solve(A, b)

// LU decomposition:
const lu = numeric.LU(A)

// SVD (Singular Value Decomposition):
const svd = numeric.svd(A)
// { S: singular values, U: left vectors, V: right vectors }

// Eigenvalues (symmetric matrix):
const eig = numeric.eig([[4, 2], [2, 4]])
// { lambda: eigenvalues, E: eigenvectors }

Optimization

import numeric from "numeric"

// Gradient descent (unconstrained minimization):
// Find minimum of f(x) = (x-3)² + (y-5)²

function objective(x: number[]) {
  return Math.pow(x[0] - 3, 2) + Math.pow(x[1] - 5, 2)
}

function gradient(x: number[]) {
  return [2 * (x[0] - 3), 2 * (x[1] - 5)]
}

// Note: numeric.js uses uncmin for unconstrained minimization
const result = numeric.uncmin(objective, [0, 0])
// result.solution ≈ [3, 5] (the minimum)

@stdlib/stdlib

@stdlib/stdlib — NumPy/SciPy-inspired scientific computing:

Installation and usage (modular packages)

// @stdlib is highly modular — install only what you need:
// npm install @stdlib/stats-base-mean @stdlib/math-base-special-gamma

// Or the full library:
// npm install @stdlib/stdlib

// Statistical distributions:
import * as norm from "@stdlib/stats-base-dists-normal"

const mu = 0      // mean
const sigma = 1   // standard deviation

norm.pdf(0, mu, sigma)    // 0.3989 (probability density at x=0)
norm.cdf(1.96, mu, sigma) // 0.975 (cumulative: 97.5% below z=1.96)
norm.quantile(0.95, mu, sigma)  // 1.645 (95th percentile)

// Random sampling:
import normal from "@stdlib/random-base-normal"
const rnorm = normal.factory(0, 1)  // N(0,1) random number generator
const samples = Array.from({ length: 1000 }, rnorm)

Statistics

import mean from "@stdlib/stats-base-mean"
import variance from "@stdlib/stats-base-variance"
import skewness from "@stdlib/stats-base-skewness"

const data = new Float64Array([4, 8, 6, 5, 3, 2, 8, 9, 2, 5])
const n = data.length

mean(n, 1, data, 1)            // 5.2 (stride=1)
variance(n, 1, mean(n, 1, data, 1), data, 1)  // Variance with known mean

Special mathematical functions

// @stdlib provides the most complete set of special functions in JS:
import gamma from "@stdlib/math-base-special-gamma"
import beta from "@stdlib/math-base-special-beta"
import erf from "@stdlib/math-base-special-erf"
import bessel from "@stdlib/math-base-special-besselj0"

gamma(5)      // 24 (4! = Gamma(5))
gamma(0.5)    // 1.7724... (√π)
beta(2, 3)    // 0.0833...
erf(1)        // 0.8427... (error function)
bessel(0)     // 1.0 (Bessel function J₀)

// These are not available in math.js or numeric.js

Feature Comparison

Featuremath.jsnumeric.js@stdlib
Expression parser
Symbolic math
Matrices✅ (specialized)
Linear algebra✅ Excellent
Statistics✅ Basic✅ Excellent
Distributions✅ Many
Special functions✅ Some✅ Most complete
Random sampling
Units
Complex numbers
BigNumber
Tree-shakable⚠️
TypeScript✅ @types
Bundle size~150KB~60KBModular

When to Use Each

Choose math.js if:

  • You need a user-facing expression evaluator (calculator, formula fields)
  • Working with physical units and unit conversion
  • Complex numbers, symbolic math, or BigNumber precision
  • General-purpose math without needing the full scipy-like suite

Choose numeric.js if:

  • Specific numerical analysis: SVD, eigenvalues, LU decomposition
  • Solving linear systems at high performance
  • Already using it in the codebase for its specialized algorithms

Choose @stdlib if:

  • Scientific computing requiring statistical distributions (normal, Poisson, binomial)
  • Special mathematical functions (Gamma, Beta, Bessel, error function)
  • NumPy-style BLAS operations with typed arrays
  • Tree-shaking matters — you can import only the functions you need

Consider TensorFlow.js for ML math:

// For machine learning operations (tensor math, GPU acceleration):
// @tensorflow/tfjs provides GPU-accelerated matrix operations
// Math.js/numeric.js are CPU-only — TF.js for ML pipelines

import * as tf from "@tensorflow/tfjs"
const matrix = tf.tensor2d([[1, 2], [3, 4]])
const result = tf.matMul(matrix, tf.transpose(matrix))

math.js Expression Parser in Production: User-Defined Formulas

The expression parser is math.js's most distinctive capability — the ability to evaluate a math formula provided as a string safely and correctly. This is not just a convenience; it's a genuine product feature that enables user-configurable calculations in dashboards, spreadsheet-like interfaces, and scoring systems.

The parser handles operator precedence, function calls, variable substitution, and unit conversions correctly. It safely sandboxes execution — unlike eval(), which would expose the entire JavaScript runtime, the math.js parser only executes within math.js's defined function and constant namespace. Injecting a formula string from user input is safe as long as you use evaluate() or compile() rather than eval().

In a package analytics context, this lets users define custom health score formulas: "weeklyDownloads * 0.4 + stars * 0.3 + issueRatio * 0.3" where issueRatio is a pre-computed variable. The formula is stored as a string, validated by attempting to compile it, and evaluated at render time with fresh data. The compile() function pre-parses the expression so repeated evaluation with different variable sets is efficient — comparable in speed to a native JavaScript function for simple arithmetic.

The parser also handles unit-aware expressions. evaluate("100 km/h to mph") returns 62.137... with units attached. For scientific applications that mix physical quantities — computing energy consumption in joules, converting between unit systems, or ensuring dimensional consistency in derived formulas — this is functionality that no other JavaScript math library provides natively.

@stdlib's Typed Array Orientation and BLAS-Style APIs

@stdlib/stdlib takes an approach that looks unfamiliar to JavaScript developers coming from NumPy or even from math.js, but it makes sense once you understand the performance rationale.

Many @stdlib statistical functions take (n, stride, array, stride) arguments rather than a plain array. For example, mean(n, 1, data, 1) computes the mean of a Float64Array where n is the count and the stride values allow operating on a sub-array or column of a matrix stored in flat memory. This BLAS-style API (Basic Linear Algebra Subprograms, the same interface used by NumPy's underlying Fortran libraries) enables zero-copy operations on contiguous memory, which matters for large scientific datasets.

The practical implication is that @stdlib functions perform best when you provide Float64Array or Float32Array inputs rather than regular JavaScript arrays. Standard number[] arrays work too, but typed arrays avoid the garbage collector pressure of boxing and unboxing JavaScript Number objects for each element. For datasets with thousands or millions of values — genomics data, sensor readings, financial tick data — typed arrays and the BLAS-style API give @stdlib a meaningful throughput advantage over math.js's more ergonomic but less cache-friendly approach.

@stdlib's modularity is also worth understanding in depth. The monorepo publishes over 4,000 individual packages under the @stdlib namespace. You can install @stdlib/stats-base-mean (800 bytes) to compute a mean, or @stdlib/math-base-special-gamma (6KB) for the Gamma function, without pulling in any code you don't use. This makes @stdlib uniquely suited for browser-side scientific computing where bundle size is constrained but statistical depth is required.

Bundle Size and Tree-Shaking Realities

Bundle size is a practical constraint for browser-based scientific computing, and the three libraries have very different stories here.

math.js's core bundle, even when using selective imports from the create() API, is approximately 150-200KB minified and gzipped. The expression parser alone contributes a significant portion of this. For Node.js server applications, this is irrelevant, but for a browser-side calculator or data visualization tool, 150KB for math is a non-trivial allocation. The create() API with all loads the full library; the recommended approach for smaller bundles is to use create() with only the specific function collections you need (algebraDependencies, statisticsDependencies, etc.), which can reduce the bundle to 30-50KB for a subset of functionality.

numeric.js is around 60KB minified but is not tree-shakable — it ships as a single object, so importing it pulls in all decompositions, solvers, and optimizers whether you use them or not. For its specialized use cases (SVD, eigenvalue decomposition, unconstrained optimization), the 60KB is acceptable, but there's no way to import only the solver.

@stdlib's modular package approach is the most tree-shaking-friendly. If you install only @stdlib/stats-base-mean and @stdlib/math-base-special-erf, you pay only for those functions. The ecosystem tooling (bundlers like Rollup and esbuild) can dead-code-eliminate unused parts of even @stdlib/stdlib if it's imported with named imports, though installing individual packages is the most reliable approach for minimal footprint.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on mathjs v13.x, numeric.js v1.x, and @stdlib/stdlib v0.3.x.

Compare math and scientific computing packages on PkgPulse →

A useful decision heuristic: if your math operations involve user-provided formula strings (a calculator, a spreadsheet-like interface, a custom scoring system), math.js is the only library in this group that handles this safely. If your operations are all hard-coded numeric algorithms on known data shapes — fitting a regression, decomposing a matrix, running PCA — then numeric.js or @stdlib will be faster with less overhead. If you need to ship to both Node.js and the browser and bundle size is a real constraint (the application already has a large JavaScript payload), @stdlib's individual package model lets you add only the functions you need without pulling in a 150KB math library. For server-side data pipelines where bundle size is irrelevant, math.js's ergonomics and expression parser make it the most versatile starting point, and you can always optimize specific bottlenecks with typed array-based alternatives later.

See also: AVA vs Jest and ohash vs object-hash vs hash-wasm, acorn vs @babel/parser vs espree.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.