<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/6.6.1/math.min.js"></script>
const supportedOperators = {
'-': {
precedence: 1,
operate: (lhs, rhs) => lhs - rhs,
},
'+': {
precedence: 1,
operate: (lhs, rhs) => lhs + rhs,
},
'/': {
precedence: 3,
operate: (lhs, rhs) => lhs / rhs,
},
'*': {
precedence: 4,
operate: (lhs, rhs) => lhs * rhs,
},
'^': {
precedence: 5,
operate: (lhs, rhs) => lhs ** rhs,
},
};
function isOperator(value) {
return !!supportedOperators[value];
}
function isOperand(value) {
return !Number.isNaN(+value);
}
function getPrecedence(operator) {
const supportedOperator = supportedOperators[operator];
if (!supportedOperator) {
return -1;
}
return supportedOperator.precedence;
}
function evaluateLastOperation(operators, operands) {
const operator = supportedOperators[operators.pop()];
const rhs = operands.pop();
const lhs = operands.pop();
return operator.operate(+lhs, +rhs);
}
function tokenizeExpression(expr) {
const currentTokens = [];
let currentValue = '';
for (let i = 0; i < expr.length; i += 1) {
if (expr[i] === ' ') {
if (currentValue) {
currentTokens.push(currentValue);
currentValue = '';
}
} else if (expr[i] === ')') {
if (currentValue) {
currentTokens.push(currentValue);
currentValue = '';
}
currentTokens.push(')');
} else if (isOperand(expr[i]) ||
(expr[i] === '.' && currentValue.indexOf('.') === -1 && expr[i + 1]) ||
(expr[i] === '-' && currentValue.indexOf('-') === -1 && isOperand(expr[i + 1]))) {
currentValue += expr[i];
} else if (expr[i] === '(' || expr[i] === ')' || isOperator(expr[i])) {
currentTokens.push(expr[i]);
}
}
if (currentValue) {
currentTokens.push(currentValue);
}
return currentTokens;
}
function evaluateExpression(expr) {
const tokens = tokenizeExpression(expr);
const operands = [];
const operators = [];
tokens.forEach((token) => {
if (isOperand(token)) {
operands.push(token);
} else if (token === '(') {
operators.push(token);
} else if (token === ')') {
let operatorTop = operators[operators.length - 1];
while (operators.length && operatorTop !== '(') {
operands.push(evaluateLastOperation(operators, operands));
operatorTop = operators[operators.length - 1];
}
operators.pop();
} else if (isOperator(token)) {
let operatorTop = operators[operators.length - 1];
while (operators.length && operatorTop !== '(' && getPrecedence(token) <= getPrecedence(operatorTop)) {
operands.push(evaluateLastOperation(operators, operands));
operatorTop = operators[operators.length - 1];
}
operators.push(token);
}
});
while (operators.length) {
operands.push(evaluateLastOperation(operators, operands));
}
return operands[0];
}
var testMathExpression = `142 * ( 2 / 0.9 ) ^ -1.2 * 0.9938 ^ 50`;
var func = new Function(`return ${testMathExpression}`);
eval(testMathExpression);
func();
math.evaluate(testMathExpression);
evaluateExpression(testMathExpression);
--enable-precise-memory-info
flag.
Test case name | Result |
---|---|
eval | |
new Function | |
mathjs | |
evaluateExpression |
Test name | Executions per second |
---|---|
eval | 7952271.0 Ops/sec |
new Function | 29537648.0 Ops/sec |
mathjs | 154031.1 Ops/sec |
evaluateExpression | 170868.0 Ops/sec |
Benchmark Overview
The provided benchmark measures the performance of different approaches to evaluate mathematical expressions in JavaScript: eval
, new Function
, mathjs
(a library for symbolic computation), and evaluateExpression
(a custom implementation).
Tested Options
eval(testMathExpression)
: This method uses the built-in eval()
function to execute a string as JavaScript code.func();
: This method creates a new anonymous function using Function
constructor and passes the mathematical expression as a string argument.math.evaluate(testMathExpression);
: This method uses the evaluate()
function from the mathjs
library to evaluate the mathematical expression.evaluateExpression(testMathExpression);
: This is a custom implementation that tokenizes the expression, then evaluates it using a stack-based algorithm.Pros and Cons of Each Approach
eval()
:new Function()
:eval()
, allows for sandboxing the expression.eval()
due to function creation overhead.mathjs
:evaluateExpression()
:Benchmark Results
The provided benchmark results show the number of executions per second for each test case on a mobile Safari 17 browser:
Test Case | Executions Per Second |
---|---|
new Function |
29,537,648 |
eval |
7,952,271 |
evaluateExpression |
1,708,68 |
mathjs |
15,403,11 |
The results suggest that the custom implementation (evaluateExpression
) is significantly slower than the other approaches. However, it's essential to note that this benchmark may not be representative of all use cases or environments.
Conclusion
The choice of evaluation method depends on the specific requirements and constraints of your project. If you need a simple, widely supported solution, eval()
might be sufficient. For more secure and efficient evaluations, consider using new Function()
or mathjs
. The custom implementation (evaluateExpression
) may not be the best choice for most use cases, but it could provide optimization opportunities for specific scenarios.