decimal.js, mathjs, and numeric are JavaScript libraries designed to handle mathematical operations that go beyond the native Number type. decimal.js focuses on arbitrary-precision decimal arithmetic, solving floating-point errors in financial or scientific calculations. mathjs offers a broad expression parser and support for matrices, units, and complex numbers, acting as a comprehensive math engine. numeric is an older library providing numerical analysis functions like linear algebra and optimization, but it lacks modern maintenance compared to the others.
JavaScript's native Number type uses IEEE 754 double-precision floating-point format. This works for most UI logic, but it fails in scenarios requiring exact decimal representation or advanced linear algebra. For example, 0.1 + 0.2 equals 0.30000000000000004 in native JavaScript. To solve this, developers turn to libraries like decimal.js, mathjs, and numeric. Each takes a different approach to math in the browser and Node.js.
decimal.js is a single-purpose tool.
// decimal.js: Exact decimal math
import Decimal from 'decimal.js';
const x = new Decimal(0.1);
const y = new Decimal(0.2);
const sum = x.plus(y);
console.log(sum.toString()); // "0.3" exactly
mathjs is a comprehensive math engine.
'12.3 cm to inch'.decimal.js or similar logic internally for some precision but prioritizes feature breadth.// mathjs: Expression parsing and units
import { evaluate, unit } from 'mathjs';
const result = evaluate('0.1 + 0.2');
console.log(result); // 0.3 (handled via configuration)
const distance = unit('12.3 cm').to('inch');
console.log(distance.toString()); // "4.84251968503937 inch"
numeric is a legacy numerical analysis library.
// numeric: Linear algebra (legacy)
import numeric from 'numeric';
const A = [[1, 2], [3, 4]];
const B = [[5, 6], [7, 8]];
const product = numeric.dot(A, B);
console.log(product); // [[19, 22], [43, 50]] using native floats
The most common reason to adopt a math library is fixing floating-point drift.
decimal.js treats every number as a decimal object.
new Decimal().// decimal.js: Financial calculation
const price = new Decimal('19.99');
const tax = new Decimal('0.15');
const total = price.times(1.plus(tax));
console.log(total.toFixed(2)); // "22.99"
mathjs can handle precision but requires setup.
BigNumber (similar to decimal.js) for better accuracy.// mathjs: Configuring precision
import { create, all, config } from 'mathjs';
const math = create(all, {
number: 'BigNumber',
precision: 64
});
const result = math.add(0.1, 0.2);
console.log(result.toString()); // "0.3"
numeric does not solve floating-point errors.
Number.0.1 + 0.2 will still result in 0.30000000000000004.// numeric: Native float behavior
import numeric from 'numeric';
const sum = numeric.add(0.1, 0.2);
console.log(sum); // 0.30000000000000004
Scientific and graphics applications often need matrix operations.
decimal.js has no matrix support.
// decimal.js: No native matrix support
// You must manually map operations
const row = [new Decimal(1), new Decimal(2)];
const scaled = row.map(val => val.times(2));
// [[2, 4]] - Manual implementation required
mathjs has robust matrix support built-in.
inv(), det(), and eigs() are ready to use.// mathjs: Matrix operations
import { matrix, inv, det } from 'mathjs';
const A = matrix([[1, 2], [3, 4]]);
const inverse = inv(A);
const determinant = det(A);
console.log(determinant); // -2
numeric was designed specifically for this.
// numeric: Advanced solvers
import numeric from 'numeric';
// Solving a linear system Ax = B
const A = [[1, 2], [3, 4]];
const B = [5, 6];
const x = numeric.solve(A, B);
console.log(x); // Solution vector using native floats
How you write code differs significantly between these tools.
decimal.js uses a chainable, object-oriented API.
.plus(), .minus(), .toPrecision() are explicit.// decimal.js: Chainable API
const result = new Decimal(10)
.dividedBy(3)
.toDecimalPlaces(2);
console.log(result.toString()); // "3.33"
mathjs offers both functional and expression-based styles.
math.add(a, b) or evaluate 'a + b'.// mathjs: Functional vs Expression
import { add, evaluate } from 'mathjs';
// Functional
const a = add(2, 3);
// Expression
const b = evaluate('2 + 3');
console.log(a, b); // 5 5
numeric uses a simple, global functional API.
numeric object.// numeric: Global functional API
import numeric from 'numeric';
const mag = numeric.norm2([3, 4]);
console.log(mag); // 5
Long-term support is critical for architectural decisions.
decimal.js is actively maintained.
mathjs is also actively maintained.
numeric is effectively deprecated.
mathjs or ndarray.| Feature | decimal.js | mathjs | numeric |
|---|---|---|---|
| Primary Goal | Exact decimal precision | Broad math engine | Numerical analysis |
| Precision | Arbitrary (Configurable) | Native or BigNumber | Native Floats |
| Matrices | ❌ No | ✅ Yes (Dense/Sparse) | ✅ Yes |
| Units | ❌ No | ✅ Yes (Physical units) | ❌ No |
| Expression Parser | ❌ No | ✅ Yes | ❌ No |
| Maintenance | ✅ Active | ✅ Active | ❌ Inactive |
| Best For | Money, Finance | Science, Engineering | Legacy Code |
decimal.js is the specialist 🎯. Use it when correctness matters more than features. If you are building a checkout cart, a ledger, or anything involving money, this is the only safe choice among the three.
mathjs is the generalist 🛠️. Use it when you need a calculator in your app. It handles units, matrices, and expressions beautifully. It is perfect for educational tools, engineering dashboards, or scientific simulations.
numeric is the legacy tool 🕰️. It solved hard problems in the past, but it has been surpassed. Unless you are maintaining an old codebase, skip it. For linear algebra in new projects, mathjs or specialized modern libraries are better paths.
Final Thought: Precision and features are often trade-offs. decimal.js gives you accuracy but no matrices. mathjs gives you everything but requires more setup for strict precision. numeric gives you neither accuracy nor maintenance. Choose based on whether you need exact numbers or advanced math.
Choose decimal.js when you need exact decimal precision, such as in financial applications or when dealing with currency. It is the best fit if your primary concern is avoiding floating-point rounding errors (e.g., 0.1 + 0.2 !== 0.3) and you do not need complex matrix operations or unit conversions. It is lightweight and focused solely on decimal arithmetic.
Choose mathjs if you need a versatile math engine that supports expressions, matrices, complex numbers, and physical units. It is ideal for scientific calculations, engineering apps, or scenarios where you need to parse and evaluate string-based math formulas. It balances precision with a wide feature set, though it is heavier than decimal.js.
Avoid using numeric for new projects as it is largely unmaintained and outdated compared to modern alternatives. It was historically used for linear algebra and optimization, but mathjs or specialized libraries like ndarray or linalg are better choices today. Only consider it if you are maintaining legacy code that already depends on its specific API.

An arbitrary-precision Decimal type for JavaScript.
Number.prototype and Math objects
The library is similar to bignumber.js, but here precision is specified in terms of significant digits rather than decimal places, and all calculations are rounded to the precision (similar to Python's decimal module) rather than just those involving division.
This library also adds the trigonometric functions, among others, and supports non-integer powers, which makes it a significantly larger library than bignumber.js and the even smaller big.js.
For a lighter version of this library without the trigonometric functions see decimal.js-light.
The library is the single JavaScript file decimal.js or ES module decimal.mjs.
Browser:
<script src='path/to/decimal.js'></script>
<script type="module">
import Decimal from './path/to/decimal.mjs';
...
</script>
npm install decimal.js
const Decimal = require('decimal.js');
import Decimal from 'decimal.js';
import {Decimal} from 'decimal.js';
In all examples below, semicolons and toString calls are not shown.
If a commented-out value is in quotes it means toString has been called on the preceding expression.
The library exports a single constructor function, Decimal, which expects a single argument that is a number, string or Decimal instance.
x = new Decimal(123.4567)
y = new Decimal('123456.7e-3')
z = new Decimal(x)
x.equals(y) && y.equals(z) && x.equals(z) // true
If using values with more than a few digits, it is recommended to pass strings rather than numbers to avoid a potential loss of precision.
// Precision loss from using numeric literals with more than 15 significant digits.
new Decimal(1.0000000000000001) // '1'
new Decimal(88259496234518.57) // '88259496234518.56'
new Decimal(99999999999999999999) // '100000000000000000000'
// Precision loss from using numeric literals outside the range of Number values.
new Decimal(2e+308) // 'Infinity'
new Decimal(1e-324) // '0'
// Precision loss from the unexpected result of arithmetic with Number values.
new Decimal(0.7 + 0.1) // '0.7999999999999999'
As with JavaScript numbers, strings can contain underscores as separators to improve readability.
x = new Decimal('2_147_483_647')
String values in binary, hexadecimal or octal notation are also accepted if the appropriate prefix is included.
x = new Decimal('0xff.f') // '255.9375'
y = new Decimal('0b10101100') // '172'
z = x.plus(y) // '427.9375'
z.toBinary() // '0b110101011.1111'
z.toBinary(13) // '0b1.101010111111p+8'
// Using binary exponential notation to create a Decimal with the value of `Number.MAX_VALUE`.
x = new Decimal('0b1.1111111111111111111111111111111111111111111111111111p+1023')
// '1.7976931348623157081e+308'
Decimal instances are immutable in the sense that they are not changed by their methods.
0.3 - 0.1 // 0.19999999999999998
x = new Decimal(0.3)
x.minus(0.1) // '0.2'
x // '0.3'
The methods that return a Decimal can be chained.
x.dividedBy(y).plus(z).times(9).floor()
x.times('1.23456780123456789e+9').plus(9876.5432321).dividedBy('4444562598.111772').ceil()
Many method names have a shorter alias.
x.squareRoot().dividedBy(y).toPower(3).equals(x.sqrt().div(y).pow(3)) // true
x.comparedTo(y.modulo(z).negated() === x.cmp(y.mod(z).neg()) // true
Most of the methods of JavaScript's Number.prototype and Math objects are replicated.
x = new Decimal(255.5)
x.toExponential(5) // '2.55500e+2'
x.toFixed(5) // '255.50000'
x.toPrecision(5) // '255.50'
Decimal.sqrt('6.98372465832e+9823') // '8.3568682281821340204e+4911'
Decimal.pow(2, 0.0979843) // '1.0702770511687781839'
// Using `toFixed()` to avoid exponential notation:
x = new Decimal('0.0000001')
x.toString() // '1e-7'
x.toFixed() // '0.0000001'
And there are isNaN and isFinite methods, as NaN and Infinity are valid Decimal values.
x = new Decimal(NaN) // 'NaN'
y = new Decimal(Infinity) // 'Infinity'
x.isNaN() && !y.isNaN() && !x.isFinite() && !y.isFinite() // true
There is also a toFraction method with an optional maximum denominator argument.
z = new Decimal(355)
pi = z.dividedBy(113) // '3.1415929204'
pi.toFraction() // [ '7853982301', '2500000000' ]
pi.toFraction(1000) // [ '355', '113' ]
All calculations are rounded according to the number of significant digits and rounding mode specified
by the precision and rounding properties of the Decimal constructor.
For advanced usage, multiple Decimal constructors can be created, each with their own independent configuration which applies to all Decimal numbers created from it.
// Set the precision and rounding of the default Decimal constructor
Decimal.set({ precision: 5, rounding: 4 })
// Create another Decimal constructor, optionally passing in a configuration object
Dec = Decimal.clone({ precision: 9, rounding: 1 })
x = new Decimal(5)
y = new Dec(5)
x.div(3) // '1.6667'
y.div(3) // '1.66666666'
The value of a Decimal is stored in a floating point format in terms of its digits, exponent and sign, but these properties should be considered read-only.
x = new Decimal(-12345.67);
x.d // [ 12345, 6700000 ] digits (base 10000000)
x.e // 4 exponent (base 10)
x.s // -1 sign
For further information see the API reference in the doc directory.
To run the tests using Node.js from the root directory:
npm test
Each separate test module can also be executed individually, for example:
node test/modules/toFraction
To run the tests in a browser, open test/test.html.
Two minification examples:
Using uglify-js to minify the decimal.js file:
npm install uglify-js -g
uglifyjs decimal.js --source-map url=decimal.min.js.map -c -m -o decimal.min.js
Using terser to minify the ES module version, decimal.mjs:
npm install terser -g
terser decimal.mjs --source-map url=decimal.min.mjs.map -c -m --toplevel -o decimal.min.mjs
import Decimal from './decimal.min.mjs';