ndarray vs mathjs vs numeric
Scientific and Numerical Computing Libraries for JavaScript
ndarraymathjsnumericSimilar Packages:

Scientific and Numerical Computing Libraries for JavaScript

mathjs, ndarray, and numeric are JavaScript libraries designed to support mathematical, scientific, and numerical computing in browser or Node.js environments. They provide capabilities beyond native JavaScript's limited math support, such as matrix operations, linear algebra, complex numbers, and array manipulation. While they share overlapping goals, their design philosophies, APIs, and target use cases differ significantly — especially in how they handle data structures, performance, and extensibility.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
ndarray932,1561,246-226 years agoMIT
mathjs015,0099.41 MB173a month agoApache-2.0
numeric01,448-6913 years ago-

Scientific Computing in JavaScript: mathjs vs ndarray vs numeric

When you need to go beyond basic arithmetic in JavaScript — whether for data analysis, engineering calculations, or interactive visualizations — you’ll quickly hit the limits of native Math. That’s where specialized libraries come in. mathjs, ndarray, and numeric each aim to bring scientific computing to the browser, but they take very different approaches. Let’s break down how they compare in real-world usage.

⚠️ Maintenance Status: Don’t Start With a Dead End

Before diving into features, check if the project is alive.

  • numeric is deprecated. Its GitHub repository states: "This library is no longer maintained." The last meaningful commit was years ago. Do not use it in new projects.
  • mathjs is actively maintained, with regular releases and strong documentation.
  • ndarray is part of the scijs ecosystem, which sees occasional updates and remains usable, though development pace is slower than mathjs.

Bottom line: Rule out numeric immediately. The comparison below focuses on mathjs and ndarray for practical decision-making.

🧮 Core Philosophy: Expressiveness vs Performance

mathjs: "Write Math Like Math"

mathjs prioritizes readability and expressiveness. You can write equations almost as you would on paper, including support for units, complex numbers, and symbolic expressions.

// mathjs: natural syntax for math
import { evaluate, unit, complex } from 'mathjs';

// Evaluate string expressions
const result1 = evaluate('sqrt(4 + 5^2)'); // 5.385...

// Work with units
const speed = unit('55 km/h').to('m/s'); // ~15.28 m/s

// Complex numbers
const z = complex(2, 3);
const w = z.pow(2); // -5 + 12i

It includes a full expression parser, so users can input formulas directly — great for calculators or config-driven computations.

ndarray: "Arrays First, Everything Else Later"

ndarray takes a low-level, performance-first approach inspired by NumPy. It provides a memory-efficient n-dimensional array structure but no built-in math functions. You must pair it with other scijs packages (like ndarray-ops, ndarray-linear-solve) for actual computation.

// ndarray: manual array creation and ops
import ndarray from 'ndarray';
import ops from 'ndarray-ops';

// Create a 2x2 matrix
const A = ndarray(new Float64Array([1, 2, 3, 4]), [2, 2]);

// Multiply every element by 2
ops.mulseq(A, 2); // A is modified in place

// No built-in .dot() or .inv() — need extra packages

This modularity keeps bundles small but shifts complexity to the developer.

🔢 Matrix and Array Operations

Creating and Manipulating Matrices

mathjs uses its own Matrix type with chainable methods:

// mathjs matrix
import { matrix, multiply, inv } from 'mathjs';

const A = matrix([[1, 2], [3, 4]]);
const B = matrix([[5], [6]]);
const x = multiply(inv(A), B); // Solve Ax = B

ndarray treats matrices as views over typed arrays:

// ndarray matrix
import ndarray from 'ndarray';
import solve from 'ndarray-linear-solve';

const A = ndarray(new Float64Array([1, 2, 3, 4]), [2, 2]);
const b = ndarray(new Float64Array([5, 6]), [2]);
const x = solve(A, b); // Returns new ndarray

Note: ndarray doesn’t include solve by default — you must install ndarray-linear-solve separately.

Element-wise Operations

mathjs handles broadcasting automatically:

// mathjs: add scalar to matrix
const M = matrix([[1, 2], [3, 4]]);
const result = M.add(10); // [[11, 12], [13, 14]]

ndarray requires explicit loops or ndarray-ops:

// ndarray: add scalar
import ops from 'ndarray-ops';
const M = ndarray(new Float32Array([1, 2, 3, 4]), [2, 2]);
ops.addeq(M, 10); // Modifies M in place

📐 Advanced Math Features

Units and Physical Quantities

Only mathjs supports units natively:

// mathjs units
import { unit } from 'mathjs';

const length = unit('5 km');
const time = unit('2 hours');
const speed = length.divide(time).to('m/s'); // ~0.694 m/s

ndarray has no concept of units — you’d need to track them manually.

Complex Numbers

mathjs has first-class complex number support:

// mathjs complex
import { complex, sqrt } from 'mathjs';

const z = complex(-1, 0);
const root = sqrt(z); // 0 + i

ndarray can store complex data using interleaved real/imaginary values, but you need cwise or custom code to operate on them — no built-in complex arithmetic.

Expression Parsing

mathjs includes a powerful expression evaluator:

// mathjs parsing
import { parse } from 'mathjs';

const node = parse('x^2 + y');
const code = node.compile();
const result = code.evaluate({ x: 3, y: 4 }); // 13

ndarray offers nothing in this space — it’s purely for array data structures.

⚙️ Performance Considerations

  • mathjs is not optimized for large-scale numerical workloads. Its focus on flexibility and safety means overhead per operation. Fine for small-to-medium datasets or user-facing calculations, but avoid for heavy simulation or real-time signal processing.
  • ndarray shines when you need raw speed on large arrays. By using typed arrays and in-place operations, it minimizes allocations. However, you pay for this with boilerplate and fragmented tooling.

Example: multiplying two 1000×1000 matrices

  • mathjs: convenient but slower due to object wrapping and bounds checking.
  • ndarray + ndarray-blas: faster, but requires installing and wiring up BLAS bindings.

🧩 Ecosystem and Extensibility

  • mathjs is a batteries-included library. Most features you’d expect (statistics, probability, algebra) are built in or available via official extensions.
  • ndarray is a building block. You compose functionality from dozens of tiny scijs packages (ndarray-fill, ndarray-sort, ndarray-fourier, etc.). This avoids bloat but increases integration effort.

🛠️ Real-World Use Cases

Case 1: Web-Based Engineering Calculator

  • Best choice: mathjs
  • Why? Users enter formulas like sin(30 deg) * 5 N; units and parsing are essential.
// User types: "force = mass * acceleration"
const expr = '5 kg * 9.81 m/s^2';
const force = evaluate(expr); // 49.05 N

Case 2: Real-Time Audio Spectrum Analyzer

  • Best choice: ndarray
  • Why? You’re processing large float32 arrays from Web Audio API; performance matters more than syntax sugar.
// Process audio buffer
const samples = ndarray(audioBuffer.getChannelData(0));
fft(samples); // using ndarray-fourier

Case 3: Financial Risk Simulation (Monte Carlo)

  • Best choice: mathjs for prototyping; consider WebAssembly or Rust WASM for production scale.
  • Why? You need distributions, random sampling, and readable logic — but may outgrow JS entirely.

📌 Summary Table

Featuremathjsndarraynumeric
Status✅ Actively maintained⚠️ Low activity, usable❌ Deprecated
SyntaxHigh-level, expressiveLow-level, NumPy-likeMedium-level
Units✅ Built-in❌ None❌ None
Complex Numbers✅ First-class⚠️ Manual handling✅ Basic support
Expression Parser✅ Yes❌ No❌ No
Performance⚠️ Moderate (safe, not fast)✅ High (with right packages)⚠️ Outdated
Bundle Impact⚠️ Large (feature-rich)✅ Small (modular)✅ Small (but obsolete)
Best ForCalculators, education, scriptingImage/audio processing, simulationLegacy projects only

💡 Final Recommendation

  • Need to compute sqrt(16 m^2) or let users type det([[a,b],[c,d]])? → Use mathjs.
  • Processing gigabytes of sensor data in the browser? → Use ndarray with targeted scijs modules.
  • Considering numeric because it looks simple? → Don’t. Its age shows in API design and compatibility. Modern alternatives exist.

In most frontend scenarios — dashboards, interactive tools, educational apps — mathjs delivers the best balance of power and usability. Reserve ndarray for performance-critical numerical kernels where you’re willing to trade convenience for speed.

How to Choose: ndarray vs mathjs vs numeric

  • ndarray:

    Choose ndarray when you're building performance-sensitive applications that rely heavily on multidimensional array operations (e.g., image processing, signal analysis, or physics simulations) and you’re comfortable working with lower-level, NumPy-inspired APIs. It integrates well with the scijs ecosystem and gives fine-grained control over memory layout, but requires manual management of operations and lacks built-in high-level math functions.

  • mathjs:

    Choose mathjs when you need a high-level, expressive math library that supports symbolic computation, units, complex numbers, and a flexible expression parser. It’s ideal for applications like calculators, educational tools, engineering simulations, or any scenario where readability and mathematical expressiveness matter more than raw speed. Its API is intuitive for developers without deep numerical computing backgrounds.

  • numeric:

    Avoid numeric in new projects. The library has been officially deprecated by its author and is no longer maintained. While it once provided a solid set of numerical routines (like linear solvers and optimization), its outdated codebase, lack of TypeScript support, and absence of modern JavaScript patterns make it unsuitable for production use today. Evaluate mathjs or ndarray instead.

README for ndarray

ndarray

Modular multidimensional arrays for JavaScript.

browser support

build status

stable

Browse a number of ndarray-compatible modules in the scijs documentation
Coming from MATLAB or numpy? See: scijs/ndarray for MATLAB users
Big list of ndarray modules

Introduction

ndarrays provide higher dimensional views of 1D arrays. For example, here is how you can turn a length 4 typed array into an nd-array:

var mat = ndarray(new Float64Array([1, 0, 0, 1]), [2,2])

//Now:
//
// mat = 1 0
//       0 1
//

Once you have an nd-array you can access elements using .set and .get. For example, here is an implementation of Conway's game of life using ndarrays:

function stepLife(next_state, cur_state) {

  //Get array shape
  var nx = cur_state.shape[0], 
      ny = cur_state.shape[1]

  //Loop over all cells
  for(var i=1; i<nx-1; ++i) {
    for(var j=1; j<ny-1; ++j) {

      //Count neighbors
      var n = 0
      for(var dx=-1; dx<=1; ++dx) {
        for(var dy=-1; dy<=1; ++dy) {
          if(dx === 0 && dy === 0) {
            continue
          }
          n += cur_state.get(i+dx, j+dy)
        }
      }
      
      //Update state according to rule
      if(n === 3 || n === 3 + cur_state.get(i,j)) {
        next_state.set(i,j,1)
      } else {
        next_state.set(i,j,0)
      }
    }
  }
}

You can also pull out views of ndarrays without copying the underlying elements. Here is an example showing how to update part of a subarray:

var x = ndarray(new Float32Array(25), [5, 5])
var y = x.hi(4,4).lo(1,1)

for(var i=0; i<y.shape[0]; ++i) {
  for(var j=0; j<y.shape[1]; ++j) {
    y.set(i,j,1)
  }
}

//Now:
//    x = 0 0 0 0 0
//        0 1 1 1 0
//        0 1 1 1 0
//        0 1 1 1 0
//        0 0 0 0 0

ndarrays can be transposed, flipped, sheared and sliced in constant time per operation. They are useful for representing images, audio, volume graphics, matrices, strings and much more. They work both in node.js and with browserify.

Install

Install the library using npm:

npm install ndarray

You can also use ndarrays in a browser with any tool that follows the CommonJS/node module conventions. The most direct way to do this is to use browserify. If you want live-reloading for faster debugging, check out beefy.

API

Once you have ndarray installed, you can use it in your project as follows:

var ndarray = require("ndarray")

Constructor

ndarray(data[, shape, stride, offset])

The default module.exports method is the constructor for ndarrays. It creates an n-dimensional array view wrapping an underlying storage type

  • data is a 1D array storage. It is either an instance of Array, a typed array, or an object that implements get(), set(), .length
  • shape is the shape of the view (Default: data.length)
  • stride is the resulting stride of the new array. (Default: row major)
  • offset is the offset to start the view (Default: 0)

Returns an n-dimensional array view of the buffer

Members

The central concept in ndarray is the idea of a view. The way these work is very similar to SciPy's array slices. Views are affine projections to 1D storage types. To better understand what this means, let's first look at the properties of the view object. It has exactly 4 variables:

  • array.data - The underlying 1D storage for the multidimensional array
  • array.shape - The shape of the typed array
  • array.stride - The layout of the typed array in memory
  • array.offset - The starting offset of the array in memory

Keeping a separate stride means that we can use the same data structure to support both row major and column major storage

Element Access

To access elements of the array, you can use the set/get methods:

array.get(i,j,...)

Retrieves element i,j,... from the array. In psuedocode, this is implemented as follows:

function get(i,j,...) {
  return this.data[this.offset + this.stride[0] * i + this.stride[1] * j + ... ]
}

array.set(i,j,...,v)

Sets element i,j,... to v. Again, in psuedocode this works like this:

function set(i,j,...,v) {
  return this.data[this.offset + this.stride[0] * i + this.stride[1] * j + ... ] = v
}

array.index(i,j, ...)

Retrieves the index of the cell in the underlying ndarray. In JS,

function index(i,j, ...) {
  return this.offset + this.stride[0] * i + this.stride[1] * j + ...
}

Properties

The following properties are created using Object.defineProperty and do not take up any physical memory. They can be useful in calculations involving ndarrays

array.dtype

Returns a string representing the undelying data type of the ndarray. Excluding generic data stores these types are compatible with typedarray-pool. This is mapped according to the following rules:

Data typeString
Int8Array"int8"
Int16Array"int16"
Int32Array"int32"
Uint8Array"uint8"
Uint16Array"uint16"
Uint32Array"uint32"
BigInt64Array"bigint64"
BigUint64Array"biguint64"
Float32Array"float32"
Float64Array"float64"
Array"array"
Uint8ArrayClamped"uint8_clamped"
Buffer"buffer"
Other"generic"

Generic arrays access elements of the underlying 1D store using get()/set() instead of array accessors.

array.size

Returns the size of the array in logical elements.

array.order

Returns the order of the stride of the array, sorted in ascending length. The first element is the first index of the shortest stride and the last is the index the longest stride.

array.dimension

Returns the dimension of the array.

Slicing

Given a view, we can change the indexing by shifting, truncating or permuting the strides. This lets us perform operations like array reversals or matrix transpose in constant time (well, technically O(shape.length), but since shape.length is typically less than 4, it might as well be). To make life simpler, the following interfaces are exposed:

array.lo(i,j,k,...)

This creates a shifted view of the array. Think of it as taking the upper left corner of the image and dragging it inward by an amount equal to (i,j,k...).

array.hi(i,j,k,...)

This does the dual of array.lo(). Instead of shifting from the top-left, it truncates from the bottom-right of the array, returning a smaller array object. Using hi and lo in combination lets you select ranges in the middle of an array.

Note: hi and lo do not commute. In general:

a.hi(3,3).lo(3,3)  !=  a.lo(3,3).hi(3,3)

array.step(i,j,k...)

Changes the stride length by rescaling. Negative indices flip axes. For example, here is how you create a reversed view of a 1D array:

var reversed = a.step(-1)

You can also change the step size to be greater than 1 if you like, letting you skip entries of a list. For example, here is how to split an array into even and odd components:

var evens = a.step(2)
var odds = a.lo(1).step(2)

array.transpose(p0, p1, ...)

Finally, for higher dimensional arrays you can transpose the indices without replicating the data. This has the effect of permuting the shape and stride values and placing the result in a new view of the same data. For example, in a 2D array you can calculate the matrix transpose by:

M.transpose(1, 0)

Or if you have a 3D volume image, you can shift the axes using more generic transformations:

volume.transpose(2, 0, 1)

array.pick(p0, p1, ...)

You can also pull out a subarray from an ndarray by fixing a particular axis. The way this works is you specify the direction you are picking by giving a list of values. For example, if you have an image stored as an nxmx3 array you can pull out the channel as follows:

var red   = image.pick(null, null, 0)
var green = image.pick(null, null, 1)
var blue  = image.pick(null, null, 2)

As the above example illustrates, passing a negative or non-numeric value to a coordinate in pick skips that index.

More information

For more discussion about ndarrays, here are some talks, tutorials and articles about them:

License

(c) 2013-2016 Mikola Lysenko. MIT License