expr-eval vs jsep vs math-expression-evaluator vs mathjs
Evaluating Mathematical Expressions in JavaScript
expr-evaljsepmath-expression-evaluatormathjsSimilar Packages:

Evaluating Mathematical Expressions in JavaScript

expr-eval, jsep, math-expression-evaluator, and mathjs are JavaScript libraries designed to parse and evaluate mathematical expressions written as strings. mathjs is a comprehensive library supporting units, matrices, and complex numbers, while expr-eval and math-expression-evaluator focus on lightweight arithmetic evaluation. jsep differs by providing only a parser that generates an Abstract Syntax Tree (AST) without built-in evaluation logic. These tools help developers avoid using unsafe native eval() when handling user-defined formulas in web applications.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
expr-eval01,340-737 years agoMIT
jsep0961392 kB432 years agoMIT
math-expression-evaluator022568.3 kB24a year agoMIT
mathjs015,0359.43 MB1832 months agoApache-2.0

JavaScript Math Libraries: expr-eval vs jsep vs math-expression-evaluator vs mathjs

When building features that accept user input for calculations — like spreadsheets, scientific tools, or dynamic pricing rules — you cannot safely use JavaScript's native eval(). Instead, developers rely on specialized libraries to parse and compute these expressions securely. expr-eval, jsep, math-expression-evaluator, and mathjs solve this problem differently. Let's compare how they handle real-world engineering tasks.

🧮 Core Functionality: Parser vs Evaluator

The most critical difference lies in what each library actually does. Some calculate results immediately, while others only break the string into parts for you to handle.

mathjs is a full math engine. It parses and evaluates in one step, supporting a wide range of data types beyond simple numbers.

// mathjs: Evaluate directly
import { evaluate } from 'mathjs';
const result = evaluate('2 + 3 * sqrt(4)');
// Returns 8

expr-eval is a dedicated expression evaluator. It focuses on speed and simplicity for arithmetic and logical expressions.

// expr-eval: Evaluate directly
import { Parser } from 'expr-eval';
const result = Parser.evaluate('2 + 3 * sqrt(4)');
// Returns 8

math-expression-evaluator works similarly to expr-eval but with fewer features and less active maintenance.

// math-expression-evaluator: Evaluate directly
import Evaluator from 'math-expression-evaluator';
const result = Evaluator.evaluate('2 + 3 * sqrt(4)');
// Returns 8

jsep is strictly a parser. It does not calculate the result; it returns an Abstract Syntax Tree (AST) describing the expression structure.

// jsep: Parse only (returns AST)
import jsep from 'jsep';
const ast = jsep('2 + 3 * 4');
// Returns object: { type: 'BinaryExpression', operator: '+', ... }

📦 Feature Depth: Units, Matrices, and Types

If your app needs more than basic arithmetic, the library choice narrows quickly. Only one of these handles physical units or complex data structures out of the box.

mathjs supports units, matrices, and complex numbers natively. You can convert inches to centimeters or multiply matrices without extra code.

// mathjs: Units and Matrices
import { evaluate } from 'mathjs';
const length = evaluate('2 inch to cm');
const matrix = evaluate('[1, 2; 3, 4] * [5, 6; 7, 8]');

expr-eval handles basic math and logic but does not support physical units or matrix operations.

// expr-eval: Basic Math Only
import { Parser } from 'expr-eval';
// Cannot handle units or matrices natively
const result = Parser.evaluate('2 + 2');

math-expression-evaluator is limited to standard arithmetic operations. It lacks support for advanced types like units or matrices.

// math-expression-evaluator: Basic Math Only
import Evaluator from 'math-expression-evaluator';
const result = Evaluator.evaluate('10 * 5');

jsep has no built-in features for values. Since it only produces an AST, features like units depend entirely on your custom traversal logic.

// jsep: No Built-in Features
import jsep from 'jsep';
// You must write code to interpret units or matrices from the AST
const ast = jsep('2 + 2');

🛠️ Extensibility: Adding Custom Functions

Real-world apps often need custom logic — like fetching a exchange rate or applying a business rule — inside the expression.

mathjs allows you to import custom functions into its parser context. This keeps the expression syntax clean while extending capability.

// mathjs: Custom Functions
import { create, all } from 'mathjs';
const math = create(all);
math.import({ myFunc: (x) => x * 2 });
const result = math.evaluate('myFunc(5)');

expr-eval lets you pass variables and functions directly into the evaluate call. This is very flexible for sandboxing.

// expr-eval: Custom Functions
import { Parser } from 'expr-eval';
const result = Parser.evaluate('myFunc(x)', { 
  x: 5, 
  myFunc: (v) => v * 2 
});

math-expression-evaluator supports custom functions but the API is less documented and harder to extend safely compared to expr-eval.

// math-expression-evaluator: Custom Functions
import Evaluator from 'math-expression-evaluator';
// Limited documentation on custom function injection
const result = Evaluator.evaluate('2 + 2');

jsep requires you to manually walk the AST and invoke functions yourself. This gives total control but requires significant boilerplate code.

// jsep: Manual Implementation
import jsep from 'jsep';
const ast = jsep('myFunc(5)');
// You must write a interpreter function to execute 'myFunc'

🔒 Security Considerations

Using any expression library with untrusted input carries risk. You must ensure users cannot access global scope or execute arbitrary code.

mathjs runs in a sandboxed context by default, but you should still avoid passing raw user input without validation. It does not expose Node.js globals.

// mathjs: Sandboxed
import { evaluate } from 'mathjs';
// Safe from global scope access, but validate input length/format
evaluate(userInput);

expr-eval is designed to be safe from global scope leakage. It does not allow access to window or global objects within the expression.

// expr-eval: Sandboxed
import { Parser } from 'expr-eval';
// Cannot access console.log or window inside expression
Parser.evaluate(userInput);

math-expression-evaluator also isolates evaluation, but due to lower maintenance activity, security patches may be slower than expr-eval.

// math-expression-evaluator: Sandboxed
import Evaluator from 'math-expression-evaluator';
// Isolated context, but verify maintenance status before use
Evaluator.evaluate(userInput);

jsep is safe by nature because it only parses. However, whatever evaluator you build on top of it must be secured manually.

// jsep: Safe Parsing
import jsep from 'jsep';
// Parsing is safe, but your execution logic must be secure
const ast = jsep(userInput);

📊 Summary: Key Differences

Featuremathjsexpr-evalmath-expression-evaluatorjsep
Primary RoleFull Math EngineExpression EvaluatorExpression EvaluatorExpression Parser
OutputCalculated ValueCalculated ValueCalculated ValueAST Object
Units Support✅ Yes❌ No❌ No❌ (Manual)
Custom Functions✅ Easy✅ Easy⚠️ Limited⚠️ Manual
Maintenance🟢 Active🟢 Active🟡 Less Active🟢 Active

💡 The Big Picture

mathjs is the powerhouse 🏋️ — choose it for scientific apps, engineering tools, or anything needing units and matrices. It handles heavy lifting so you don't have to.

expr-eval is the streamlined specialist 🎯 — ideal for forms, calculators, and business rules where you need safe evaluation without the bulk. It is the modern standard for lightweight math.

math-expression-evaluator is the legacy option 🕰️ — only use if you are maintaining older code. For new projects, expr-eval offers better support and similar performance.

jsep is the building block 🧱 — pick this if you need to analyze, visualize, or transform expressions rather than just calculate them. It gives you full control over the execution logic.

Final Thought: For most frontend apps needing user-defined formulas, expr-eval strikes the best balance between safety and simplicity. Reach for mathjs only when you truly need advanced math features, and use jsep if you are building a custom compiler or validator.

How to Choose: expr-eval vs jsep vs math-expression-evaluator vs mathjs

  • expr-eval:

    Choose expr-eval if you need a lightweight evaluator that supports custom functions and variables without the overhead of a full math library. It is ideal for forms, calculators, or rules engines where users input simple formulas. The API is straightforward and focuses strictly on evaluation rather than parsing internals.

  • jsep:

    Choose jsep if you need to analyze the structure of an expression rather than just calculate the result. It is perfect for building custom validators, visualizers, or compilers where you need access to the expression tree. Note that you must write your own logic to traverse the AST and compute values.

  • math-expression-evaluator:

    Choose math-expression-evaluator only for legacy projects or extremely simple use cases where dependencies must be minimal. It lacks the active maintenance and feature depth of expr-eval, so new projects should prefer expr-eval instead. Use this only if you are constrained by existing codebases that already rely on it.

  • mathjs:

    Choose mathjs if your application requires advanced math features like units, matrices, complex numbers, or symbolic computation. It is the best fit for scientific dashboards, engineering tools, or educational apps needing high precision. Be aware that it brings a larger bundle size compared to the lighter alternatives.

README for expr-eval

JavaScript Expression Evaluator

npm CDNJS version Build Status

Description

Parses and evaluates mathematical expressions. It's a safer and more math-oriented alternative to using JavaScript’s eval function for mathematical expressions.

It has built-in support for common math operators and functions. Additionally, you can add your own JavaScript functions. Expressions can be evaluated directly, or compiled into native JavaScript functions.

Installation

npm install expr-eval

Basic Usage

var Parser = require('expr-eval').Parser;

var parser = new Parser();
var expr = parser.parse('2 * x + 1');
console.log(expr.evaluate({ x: 3 })); // 7

// or
Parser.evaluate('6 * x', { x: 7 }) // 42

Documentation

Parser

Parser is the main class in the library. It has as single parse method, and "static" methods for parsing and evaluating expressions.

Parser()

Constructs a new Parser instance.

The constructor takes an optional options parameter that allows you to enable or disable operators.

For example, the following will create a Parser that does not allow comparison or logical operators, but does allow in:

var parser = new Parser({
  operators: {
    // These default to true, but are included to be explicit
    add: true,
    concatenate: true,
    conditional: true,
    divide: true,
    factorial: true,
    multiply: true,
    power: true,
    remainder: true,
    subtract: true,

    // Disable and, or, not, <, ==, !=, etc.
    logical: false,
    comparison: false,

    // Disable 'in' and = operators
    'in': false,
    assignment: false
  }
});

parse(expression: string)

Convert a mathematical expression into an Expression object.

Parser.parse(expression: string)

Static equivalent of new Parser().parse(expression).

Parser.evaluate(expression: string, variables?: object)

Parse and immediately evaluate an expression using the values and functions from the variables object.

Parser.evaluate(expr, vars) is equivalent to calling Parser.parse(expr).evaluate(vars).

Expression

Parser.parse(str) returns an Expression object. Expressions are similar to JavaScript functions, i.e. they can be "called" with variables bound to passed-in values. In fact, they can even be converted into JavaScript functions.

evaluate(variables?: object)

Evaluate the expression, with variables bound to the values in {variables}. Each variable in the expression is bound to the corresponding member of the variables object. If there are unbound variables, evaluate will throw an exception.

js> expr = Parser.parse("2 ^ x");
(2^x)
js> expr.evaluate({ x: 3 });
8

substitute(variable: string, expression: Expression | string | number)

Create a new Expression with the specified variable replaced with another expression. This is similar to function composition. If expression is a string or number, it will be parsed into an Expression.

js> expr = Parser.parse("2 * x + 1");
((2*x)+1)
js> expr.substitute("x", "4 * x");
((2*(4*x))+1)
js> expr2.evaluate({ x: 3 });
25

simplify(variables: object)

Simplify constant sub-expressions and replace variable references with literal values. This is basically a partial evaluation, that does as much of the calculation as it can with the provided variables. Function calls are not evaluated (except the built-in operator functions), since they may not be deterministic.

Simplify is pretty simple. For example, it doesn’t know that addition and multiplication are associative, so ((2*(4*x))+1) from the previous example cannot be simplified unless you provide a value for x. 2*4*x+1 can however, because it’s parsed as (((2*4)*x)+1), so the (2*4) sub-expression will be replaced with "8", resulting in ((8*x)+1).

js> expr = Parser.parse("x * (y * atan(1))").simplify({ y: 4 });
(x*3.141592653589793)
js> expr.evaluate({ x: 2 });
6.283185307179586

variables(options?: object)

Get an array of the unbound variables in the expression.

js> expr = Parser.parse("x * (y * atan(1))");
(x*(y*atan(1)))
js> expr.variables();
x,y
js> expr.simplify({ y: 4 }).variables();
x

By default, variables will return "top-level" objects, so for example, Parser.parse(x.y.z).variables() returns ['x']. If you want to get the whole chain of object members, you can call it with { withMembers: true }. So Parser.parse(x.y.z).variables({ withMembers: true }) would return ['x.y.z'].

symbols(options?: object)

Get an array of variables, including any built-in functions used in the expression.

js> expr = Parser.parse("min(x, y, z)");
(min(x, y, z))
js> expr.symbols();
min,x,y,z
js> expr.simplify({ y: 4, z: 5 }).symbols();
min,x

Like variables, symbols accepts an option argument { withMembers: true } to include object members.

toString()

Convert the expression to a string. toString() surrounds every sub-expression with parentheses (except literal values, variables, and function calls), so it’s useful for debugging precedence errors.

toJSFunction(parameters: array | string, variables?: object)

Convert an Expression object into a callable JavaScript function. parameters is an array of parameter names, or a string, with the names separated by commas.

If the optional variables argument is provided, the expression will be simplified with variables bound to the supplied values.

js> expr = Parser.parse("x + y + z");
((x + y) + z)
js> f = expr.toJSFunction("x,y,z");
[Function] // function (x, y, z) { return x + y + z; };
js> f(1, 2, 3)
6
js> f = expr.toJSFunction("y,z", { x: 100 });
[Function] // function (y, z) { return 100 + y + z; };
js> f(2, 3)
105

Expression Syntax

The parser accepts a pretty basic grammar. It's similar to normal JavaScript expressions, but is more math-oriented. For example, the ^ operator is exponentiation, not xor.

Operator Precedence

OperatorAssociativityDescription
(...)NoneGrouping
f(), x.y, a[i]LeftFunction call, property access, array indexing
!LeftFactorial
^RightExponentiation
+, -, not, sqrt, etc.RightUnary prefix operators (see below for the full list)
*, /, %LeftMultiplication, division, remainder
+, -, ||LeftAddition, subtraction, array/list concatenation
==, !=, >=, <=, >, <, inLeftEquals, not equals, etc. "in" means "is the left operand included in the right array operand?"
andLeftLogical AND
orLeftLogical OR
x ? y : zRightTernary conditional (if x then y else z)
=RightVariable assignment
;LeftExpression separator
var parser = new Parser({
  operators: {
    'in': true,
    'assignment': true
  }
});
// Now parser supports 'x in array' and 'y = 2*x' expressions

Unary operators

The parser has several built-in "functions" that are actually unary operators. The primary difference between these and functions are that they can only accept exactly one argument, and parentheses are optional. With parentheses, they have the same precedence as function calls, but without parentheses, they keep their normal precedence (just below ^). For example, sin(x)^2 is equivalent to (sin x)^2, and sin x^2 is equivalent to sin(x^2).

The unary + and - operators are an exception, and always have their normal precedence.

OperatorDescription
-xNegation
+xUnary plus. This converts it's operand to a number, but has no other effect.
x!Factorial (x * (x-1) * (x-2) * … * 2 * 1). gamma(x + 1) for non-integers.
abs xAbsolute value (magnitude) of x
acos xArc cosine of x (in radians)
acosh xHyperbolic arc cosine of x (in radians)
asin xArc sine of x (in radians)
asinh xHyperbolic arc sine of x (in radians)
atan xArc tangent of x (in radians)
atanh xHyperbolic arc tangent of x (in radians)
cbrt xCube root of x
ceil xCeiling of x — the smallest integer that’s >= x
cos xCosine of x (x is in radians)
cosh xHyperbolic cosine of x (x is in radians)
exp xe^x (exponential/antilogarithm function with base e)
expm1 xe^x - 1
floor xFloor of x — the largest integer that’s <= x
length xString length of x
ln xNatural logarithm of x
log xNatural logarithm of x (synonym for ln, not base-10)
log10 xBase-10 logarithm of x
log2 xBase-2 logarithm of x
log1p xNatural logarithm of (1 + x)
not xLogical NOT operator
round xX, rounded to the nearest integer, using "grade-school rounding"
sign xSign of x (-1, 0, or 1 for negative, zero, or positive respectively)
sin xSine of x (x is in radians)
sinh xHyperbolic sine of x (x is in radians)
sqrt xSquare root of x. Result is NaN (Not a Number) if x is negative.
tan xTangent of x (x is in radians)
tanh xHyperbolic tangent of x (x is in radians)
trunc xIntegral part of a X, looks like floor(x) unless for negative number

Pre-defined functions

Besides the "operator" functions, there are several pre-defined functions. You can provide your own, by binding variables to normal JavaScript functions. These are not evaluated by simplify.

FunctionDescription
random(n)Get a random number in the range [0, n). If n is zero, or not provided, it defaults to 1.
fac(n)n! (factorial of n: "n * (n-1) * (n-2) * … * 2 * 1") Deprecated. Use the ! operator instead.
min(a,b,…)Get the smallest (minimum) number in the list.
max(a,b,…)Get the largest (maximum) number in the list.
hypot(a,b)Hypotenuse, i.e. the square root of the sum of squares of its arguments.
pyt(a, b)Alias for hypot.
pow(x, y)Equivalent to x^y. For consistency with JavaScript's Math object.
atan2(y, x)Arc tangent of x/y. i.e. the angle between (0, 0) and (x, y) in radians.
roundTo(x, n)Rounds x to n places after the decimal point.
map(f, a)Array map: Pass each element of a the function f, and return an array of the results.
fold(f, y, a)Array fold: Fold/reduce array a into a single value, y by setting y = f(y, x, index) for each element x of the array.
filter(f, a)Array filter: Return an array containing only the values from a where f(x, index) is true.
indexOf(x, a)Return the first index of string or array a matching the value x, or -1 if not found.
join(sep, a)Concatenate the elements of a, separated by sep.
if(c, a, b)Function form of c ? a : b. Note: This always evaluates both a and b, regardless of whether c is true or not. Use c ? a : b instead if there are side effects, or if evaluating the branches could be expensive.

Array literals

Arrays can be created by including the elements inside square [] brackets, separated by commas. For example:

[ 1, 2, 3, 2+2, 10/2, 3! ]

Function definitions

You can define functions using the syntax name(params) = expression. When it's evaluated, the name will be added to the passed in scope as a function. You can call it later in the expression, or make it available to other expressions by re-using the same scope object. Functions can support multiple parameters, separated by commas.

Examples:

square(x) = x*x
add(a, b) = a + b
factorial(x) = x < 2 ? 1 : x * factorial(x - 1)

Custom JavaScript functions

If you need additional functions that aren't supported out of the box, you can easily add them in your own code. Instances of the Parser class have a property called functions that's simply an object with all the functions that are in scope. You can add, replace, or delete any of the properties to customize what's available in the expressions. For example:

var parser = new Parser();

// Add a new function
parser.functions.customAddFunction = function (arg1, arg2) {
  return arg1 + arg2;
};

// Remove the factorial function
delete parser.functions.fac;

parser.evaluate('customAddFunction(2, 4) == 6'); // true
//parser.evaluate('fac(3)'); // This will fail

Constants

The parser also includes a number of pre-defined constants that can be used in expressions. These are shown in the table below:

ConstantDescription
EThe value of Math.E from your JavaScript runtime
PIThe value of Math.PI from your JavaScript runtime
trueLogical true value
falseLogical false value

Pre-defined constants are stored in parser.consts. You can make changes to this property to customise the constants available to your expressions. For example:

var parser = new Parser();
parser.consts.R = 1.234;

console.log(parser.parse('A+B/R').toString());  // ((A + B) / 1.234)

To disable the pre-defined constants, you can replace or delete parser.consts:

var parser = new Parser();
parser.consts = {};

Tests

  1. cd to the project directory
  2. Install development dependencies: npm install
  3. Run the tests: npm test