mathjs、ndarray 和 numeric 都是用于 JavaScript 环境中的数值计算工具库,但它们在设计目标、API 风格和适用场景上有显著差异。mathjs 是一个功能全面的数学表达式解析与计算库,支持符号运算、单位换算和矩阵操作,适合需要灵活数学表达能力的应用。ndarray 提供了类似 NumPy 的多维数组结构,专注于高性能的数组操作和内存布局控制,常用于图像处理或科学可视化。numeric 是一个较早期的数值计算库,提供线性代数、优化和统计等基础算法,但目前已不再积极维护。
在构建涉及数学计算、数据处理或科学可视化的前端应用时,开发者常面临一个关键问题:该用哪个 JavaScript 数值库?mathjs、ndarray 和 numeric 各有特色,但它们的适用边界非常不同。本文从真实开发场景出发,深入比较三者的核心能力、API 设计和工程权衡。
首先明确一点:numeric 已不再活跃维护。其 GitHub 仓库(github.com/sloisel/numeric)最后一次重要提交在 2016 年,npm 包也无近期更新。官方未标记为“deprecated”,但社区普遍认为它已过时。新项目应避免使用 numeric,本文将其纳入仅为历史参考和迁移对比。
mathjs:全功能数学引擎mathjs 的核心优势在于 表达式解析 + 符号计算 + 单位系统。它不仅能执行数值运算,还能理解 'sin(45 deg) + 2 kg to g' 这样的字符串,并返回带单位的结果。适合:
// mathjs: 表达式解析与单位换算
import { evaluate, unit } from 'mathjs';
const result1 = evaluate('sqrt(16) + sin(pi / 4)'); // ≈ 5.707
const result2 = unit('2 kg').to('g'); // 2000 g
ndarray: 多维数组基础设施ndarray 的目标是 提供类似 NumPy 的内存高效多维数组。它本身不包含高级数学函数,而是通过配套库(如 ndarray-ops)实现逐元素操作。适合:
// ndarray: 创建二维数组并操作
import ndarray from 'ndarray';
import ops from 'ndarray-ops';
const arr = ndarray(new Float32Array(12), [3, 4]); // 3x4 矩阵
ops.mulseq(arr, 2); // 所有元素 *= 2(原地操作)
console.log(arr.get(1, 2)); // 访问 [1][2] 元素
numeric(已过时):基础数值算法集合numeric 曾提供简洁的线性代数、优化和统计函数,但 API 设计较为陈旧,且无类型定义支持。例如:
// numeric (不推荐新项目使用)
// const x = numeric.solve(A, b); // 求解线性方程组
// const min = numeric.uncmin(f, x0); // 无约束优化
由于缺乏维护,任何依赖 numeric 的新代码都应视为技术债务。
假设我们需要创建一个 2x2 矩阵并计算其行列式。
mathjs:高层抽象,支持链式调用import { matrix, det } from 'mathjs';
const m = matrix([[1, 2], [3, 4]]);
const determinant = det(m); // -2
// 也支持直接传数组:det([[1, 2], [3, 4]])
ndarray:需组合多个库,但内存可控import ndarray from 'ndarray';
import det from 'ndarray-determinant'; // 需单独安装
const m = ndarray(new Float64Array([1, 2, 3, 4]), [2, 2]);
const determinant = det(m); // -2
numeric(过时):简洁但无扩展性// const m = [[1, 2], [3, 4]];
// const determinant = numeric.det(m); // -2
💡 关键区别:
mathjs内置了完整的线性代数模块;ndarray将功能拆分为微库,灵活性高但需手动集成;numeric虽简洁但已无法获得安全更新。
mathjs:默认使用普通 JavaScript 数组,对大型矩阵效率较低。虽支持 SparseMatrix,但密集计算场景非其强项。ndarray:直接包装 TypedArray,支持 strided memory layout(如转置矩阵无需复制数据),适合高频操作。numeric:基于普通数组,无内存优化,且多年未更新性能瓶颈。例如,对 1000x1000 矩阵做逐元素乘法:
// mathjs(较慢,因对象封装开销)
const a = math.matrix(...);
const b = math.multiply(a, 2);
// ndarray(快,原地操作)
const a = ndarray(typedArray, [1000, 1000]);
ops.mulseq(a, 2); // 直接修改内存
mathjs 和 ndarray 均提供官方类型定义;numeric 无官方支持。mathjs 支持按需导入(如 import { add } from 'mathjs/function/arithmetic/add'),但配置复杂;ndarray 因微库设计天然支持摇树;numeric 为单文件,无法分割。numeric 的模块格式较旧。mathjs:抛出带上下文信息的错误(如 'Dimension mismatch (2x3 != 3x2)'),便于调试。ndarray:错误信息较底层(如索引越界直接报 undefined),需开发者自行验证。numeric:错误信息简略,且多年未改进。| 场景 | 推荐库 | 理由 |
|---|---|---|
用户输入数学公式(如 "2x + 3 = 7") | mathjs | 唯一支持表达式解析和符号计算 |
单位换算(如 "5 ft to cm") | mathjs | 内置单位系统,覆盖 200+ 单位 |
| 图像/视频帧处理 | ndarray | 内存高效,支持子区域视图 |
| 科学数据可视化(如 Plotly.js 前处理) | ndarray | 与 D3、Regl 等库生态兼容 |
| 简单线性代数(小矩阵) | mathjs | 无需额外依赖,API 直观 |
| 高性能大规模数值计算 | 考虑 WebAssembly 方案 | 三者均非最优,可评估 onnxruntime-web 或 tensorflow.js |
| 新项目中的数值计算 | 避免 numeric | 维护状态风险高 |
mathjs。它的表达式解析和单位系统能极大简化业务逻辑。ndarray。它的内存模型和生态工具链专为多维数据设计。numeric。即使它看起来“够用”,长期维护成本和安全风险远超初期便利。记住:没有“最好”的库,只有“最合适”当前场景的工具。明确你的核心需求(是解析公式?还是处理像素?),再做选择。
选择 mathjs 如果你需要在应用中支持用户输入的数学表达式(如计算器、公式编辑器)、单位换算、复数或高精度计算。它提供了丰富的内置函数和可扩展的表达式解析器,适合教育类、工程类或数据分析类前端产品。但注意其体积较大,若仅需简单矩阵运算可能过于重量级。
选择 ndarray 如果你正在处理多维数据(如图像像素、3D 体数据)并需要高效的切片、视图和原地操作。它与 cwise、ndarray-ops 等生态库配合良好,适合 WebGL 前处理、科学可视化或需要精细控制内存布局的场景。但它不提供高级数学函数(如求解方程),需自行组合底层操作。
不要在新项目中使用 numeric。根据其 GitHub 仓库和 npm 页面信息,该项目自 2016 年后已无实质性更新,且官方未声明长期维护计划。虽然它曾提供简洁的线性代数接口,但缺乏现代 TypeScript 支持、性能优化和安全更新,建议优先评估 mathjs 或基于 WebAssembly 的替代方案。
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with different data types like numbers, big numbers, complex numbers, fractions, units, and matrices. Powerful and easy to use.
Math.js can be used in both node.js and in the browser.
Install math.js using npm:
npm install mathjs
Or download mathjs via one of the CDN's listed on the downloads page:
https://mathjs.org/download.html
Math.js can be used similar to JavaScript's built-in Math library. Besides that, math.js can evaluate expressions and supports chained operations.
import {
atan2, chain, derivative, e, evaluate, log, pi, pow, round, sqrt
} from 'mathjs'
// functions and constants
round(e, 3) // 2.718
atan2(3, -3) / pi // 0.75
log(10000, 10) // 4
sqrt(-4) // 2i
pow([[-1, 2], [3, 1]], 2) // [[7, 0], [0, 7]]
derivative('x^2 + x', 'x') // 2 * x + 1
// expressions
evaluate('12 / (2.3 + 0.7)') // 4
evaluate('12.7 cm to inch') // 5 inch
evaluate('sin(45 deg) ^ 2') // 0.5
evaluate('9 / 3 + 2i') // 3 + 2i
evaluate('det([-1, 2; 3, 1])') // -7
// chaining
chain(3)
.add(4)
.multiply(2)
.done() // 14
See the Getting Started for a more detailed tutorial.
Math.js works on any ES2020 compatible JavaScript engine, including node.js, Chrome, Firefox, Safari, and Edge.
First clone the project from github:
git clone git@github.com:josdejong/mathjs.git
cd mathjs
Install the project dependencies:
npm install
Then, the project can be build by executing the build script via npm:
npm run build
This will build ESM output, CommonJS output, and the bundle math.js from the source files and put them in the folder lib.
When developing new features for mathjs, it is good to be aware of the following background information.
The code of mathjs is written in ES modules, and requires all files to have a real, relative path, meaning the files must have a *.js extension. Please configure adding file extensions on auto import in your IDE.
What mathjs tries to achieve is to offer an environment where you can do calculations with mixed data types,
like multiplying a regular number with a Complex number or a BigNumber, and work with all of those in matrices.
Mathjs also allows to add a new data type with little effort.
The solution that mathjs uses has two main ingredients:
Typed functions. All functions are created using typed-function. This makes it easier to (dynamically) create and extend a single function with new data types, automatically do type conversions on function inputs, etc. So, if you create function multiply for two numbers, you can extend it with support for multiplying your own data type, say MyDecimal. If you define a conversion from MyDecimal to number, the typed-function will automatically allow you to multiply a MyDecimal with a number.
Dependency injection. When we have a function multiply with support for MyDecimal, thanks to the dependency injection, other functions using multiply under the hood, like prod, will automatically support MyDecimal too. This also works the other way around: if you don't need the heavyweight multiply (which supports BigNumbers, matrices, etc), and you just need a plain and simple number support, you can use a lightweight implementation of multiply just for numbers, and inject that in prod and other functions.
At the lowest level, mathjs has immutable factory functions which create immutable functions. The core function math.create(...) creates a new instance having functions created from all passed factory functions. A mathjs instance is a collection of created functions. It contains a function like math.import to allow extending the instance with new functions, which can then be used in the expression parser.
A common case is to implement a new function. This involves the following steps:
./src/function/arithmetic/myNewFunction.js, where you can replace arithmetic with the proper category, and myNewFunction with the name of the new function. Add the new function to the index files ./src/factoriesAny.js and possibly ./src/factoriesNumber.js.myNewFunction.js. This documentation is used to auto generate documentation on the website. It should include a History section with one line, indicating the upcoming version number in which the function will be created../src/expression/embeddedDocs/function/arithmetic/myNewFunction.js. Add the new documentation to the index file ./src/expression/embeddedDocs/embeddedDocs.js../test/unit-tests/function/arithmetic/myNewFunction.test.js../types/index.d.ts, and write tests for it in ./test/typescript-tests/testTypes.ts. This is described in ./types/EXPLANATION.md -- make sure to read that page, as Typescript definitions must be added in multiple places in the code.npm run lint (run npm run format to fix the code style automatically).The build script currently generates two types of output:
numberFor each function, an object is generated containing the factory functions of all dependencies of the function. This allows to just load a specific set of functions, and not load or bundle any other functionality. So for example, to just create function add you can do math.create(addDependencies).
To execute tests for the library, install the project dependencies once:
npm install
Then, the tests can be executed:
npm test
To test the type definitions:
npm run test:types
Additionally, the tests can be run on FireFox using headless mode:
npm run test:browser
To run the tests remotely on LambdaTest, first set the environment variables LT_USERNAME and LT_ACCESS_KEY with your username and access key and then execute:
npm run test:lambdatest
You can separately run the code linter, though it is also executed with npm test:
npm run lint
To automatically fix linting issue, run:
npm run format
To test code coverage of the tests:
npm run coverage
To see the coverage results, open the generated report in your browser:
./coverage/lcov-report/index.html
Continuous integration tests are run on GitHub Actions and TestMu AI (formerly LambdaTest) every time a commit is pushed to GitHub. GitHub Actions runs the tests for different versions of node.js, and TestMu AI runs the tests on all major browsers.
Thanks, GitHub Actions and TestMu AI for the generous free hosting of this open source project!
mathjs is published under the Apache 2.0 license:
Copyright (C) 2013-2026 Jos de Jong <wjosdejong@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
mathjs contains a JavaScript port of the CSparse library, published under the LGPL-2.1+ license:
CSparse: a Concise Sparse matrix package.
Copyright (c) 2006, Timothy A. Davis.
http://www.suitesparse.com
--------------------------------------------------------------------------------
CSparse is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
CSparse is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this Module; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA