npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

math-expr-eval

v1.0.1

Published

A lightweight mathematical expression evaluator that works seamlessly with any math library (such as bignumber.js, decimal.js)

Readme

math-expr-eval

A lightweight mathematical expression evaluator designed to work with arbitrary computation libraries like bignumber.js, decimal.js, etc.

Design Goals

Background

In financial and scientific computing, JavaScript's native Number type suffers from precision limitations. Libraries like bignumber.js and decimal.js solve this but introduce verbose API patterns that obscure mathematical expressions.

Consider the compound interest formula P * (1 + r/n)^(n*t). With bignumber.js, it becomes:

P.times(new BigNumber(1).plus(r.dividedBy(n)).pow(n.times(t)));

This approach harms readability and increases error-proneness. While math.js provides an elegant eval solution, its large size (100KB+) is prohibitive for many projects.

math-expr-eval addresses this gap—a lightweight evaluator focused specifically on mathematical expressions, enabling intuitive high-precision computations.

Core Design Objectives

This library specializes in mathematical expression evaluation. It deliberately supports only numeric computation and data access syntax, excluding control flow expressions (e.g., a ? b : c, a && b, a || b) and side-effecting statements (e.g., ++a, a++). This ensures computational purity and predictability.

Supported Expressions

| Type | Syntax | Example | | :-------------------- | :--------------------------------------------------------------- | :---------------------------------------------- | | Literals | Numbers, ...n | 123, 1.23, 0xFF, 0b101, 0o777, 123n | | Identifiers | myVar | PI, a, myVar | | Unary Expr | +, -, ~ | -a, +a, ~a | | Binary Expr | +, -, *, /, %, **, &, \|, ^, <<, >>, >>> | a + b, a * b, a & b | | Member Expr | a.b, a[b] | console.log, data['key'] | | Call Expr | fn(...) | myFunc(a, b) | | Optional Chaining | ?., ?.(), ?.[] | a?.b, myFunc?.(), data?.[key] | | Grouping | (...) | (a + b) * c |

Installation

pnpm add math-expr-eval
# or
npm install math-expr-eval

Usage Guide

Using createEvaluator with bignumber.js

import { createEvaluator } from "math-expr-eval";
import { BigNumber } from "bignumber.js";

// 1. Configure evaluator with BigNumber logic
const evaluatorOptions = {
  // Validate computable type
  isComputable: (value: unknown): value is BigNumber =>
    BigNumber.isBigNumber(value),

  // Convert values to BigNumber (handle null/undefined)
  toComputable: (value: string | number | undefined | null) => {
    if (value == null) return new BigNumber(NaN); // Optional chaining → NaN
    return new BigNumber(value);
  },

  // Define BigNumber operators
  binaryOperators: {
    "+": (a: BigNumber, b: BigNumber) => a.plus(b),
    "-": (a, b) => a.minus(b),
    "*": (a, b) => a.multipliedBy(b),
    "/": (a, b) => a.dividedBy(b),
    "%": (a, b) => a.modulo(b),
    "**": (a, b) => a.exponentiatedBy(b),
    "<<": (a, b) => new BigNumber(a.toNumber() << b.toNumber()),
    ">>": (a, b) => new BigNumber(a.toNumber() >> b.toNumber()),
    ">>>": (a, b) => new BigNumber(a.toNumber() >>> b.toNumber()),
    "|": (a, b) => new BigNumber(a.toNumber() | b.toNumber()),
    "&": (a, b) => new BigNumber(a.toNumber() & b.toNumber()),
    "^": (a, b) => new BigNumber(a.toNumber() ^ b.toNumber()),
  },
  unaryOperators: {
    "+": (a: BigNumber) => a.abs(), // Note: Absolute value
    "-": (a) => a.negated(),
    "~": (a) => new BigNumber(~a.toNumber()),
  },
};

// 2. Create evaluator
const evaluate = createEvaluator(evaluatorOptions);

// 3. Define computation context
const context = {
  rocket: {
    thrust: (p: BigNumber) => p.multipliedBy(100),
    fuel: new BigNumber(99),
  },
  GRAVITY: new BigNumber("9.8"),
};

// 4. Evaluate expression
const result = evaluate("(rocket.thrust(rocket.fuel) - GRAVITY) * 2", context);
console.log(result.toString()); // Output: 19780.4

Using createCalculator with Template Literals

import { createCalculator } from "math-expr-eval";

// 1. Create calculator using native numbers
const calc = createCalculator<number>({
  isComputable: (v): v is number => typeof v === "number",
  toComputable: (v) => {
    if (typeof v === "string") return Number.parseFloat(v);
    if (typeof v === "number") return v;
    return 0; // null/undefined → 0
  },
  binaryOperators: {
    "+": (a, b) => a + b,
    "-": (a, b) => a - b,
    "*": (a, b) => a * b,
    "/": (a, b) => a / b,
    "%": (a, b) => a % b,
    "**": (a, b) => a ** b,
    "<<": (a, b) => a << b,
    ">>": (a, b) => a >> b,
    ">>>": (a, b) => a >>> b,
    "|": (a, b) => a | b,
    "&": (a, b) => a & b,
    "^": (a, b) => a ^ b,
  },
  unaryOperators: {
    "+": (a) => a, // Identity function
    "-": (a) => -a,
    "~": (a) => ~a,
  },
});

// 2. Evaluate via template literal
const a = 10,
  b = 20.5;
const result = calc`${a} + ${b} * 2`; // = 10 + 20.5 * 2
console.log(result); // Output: 51

API Reference

Primary APIs

createEvaluator<Computable>(options: EvaluatorOptions<Computable>)

Creates an expression evaluator function.

const evaluate = createEvaluator(options);
const result = evaluate("a + b * 2", { a: 10, b: 20 });

Parameters:

  • options: EvaluatorOptions<Computable> - Configuration object

Returns:

  • (expression: string, context?: Record<string, any>) => Computable - Evaluation function

createCalculator<Computable>(options: EvaluatorOptions<Computable>)

Creates a template literal calculator.

const calc = createCalculator(options);
const result = calc`${a} + ${b} * 2`;

Parameters:

  • options: EvaluatorOptions<Computable> - Configuration object

Returns:

  • Template literal function: (strings: TemplateStringsArray, ...values: Array<Computable | Primitive>) => Computable

Parser.parse(expression: string)

Parses an expression string into an Abstract Syntax Tree (AST).

const ast = Parser.parse("a + b * 2");

Parameters:

  • expression: string - Expression to parse

Returns:

  • Expression - Root AST node

Core Type Definitions

EvaluatorOptions<Computable>

interface EvaluatorOptions<Computable> {
  // Type guard for computable values
  isComputable: (value: unknown) => value is Computable;

  // Enable expression AST caching (optional)
  cacheExpression?: boolean;

  // Value conversion handler
  toComputable: (value: string | number | undefined | null) => Computable;

  // Binary operator implementations
  binaryOperators: {
    [op in BinaryOperator]: (a: Computable, b: Computable) => Computable;
  };

  // Unary operator implementations
  unaryOperators: {
    [op in UnaryOperator]: (a: Computable) => Computable;
  };
}

Performance & Optimization

Benchmark Results

1. Computation Engine Performance

  • Number evaluator: 777,727 ops/sec
  • bignumber.js evaluator: 439,247 ops/sec
  • Performance difference: Number evaluator is 1.77x faster

Memory Usage:

  • Number evaluator: 507.9 B/op
  • BigNumber evaluator: 1.1 KB/op

2. Caching Impact

  • Standard evaluator: 153.741ms (100k iterations)
  • Cached evaluator: 37.271ms (100k iterations)
  • Improvement: Caching provides 4.1x speedup

3. Parsing Overhead

  • Simple expressions: 93.1% of total time
  • Medium complexity: 74.5%-80.0% of total time
  • Complex expressions: 64.1% of total time

Optimization Strategies

  1. Select Appropriate Engine

    • Standard precision: Use native Number
    • High precision: Use BigNumber/decimal.js (accept performance cost)
  2. Enable Expression Caching

    • Always enable cacheExpression for repeated evaluations
  3. Simplify Expressions

    • Avoid deep nesting
    • Break complex expressions into smaller parts
    • Minimize function calls and member access
  4. Run Performance Tests

    pnpm run benchmark:demo  # View detailed reports

Critical Notes & Limitations

⚠️ Security Warning

This library allows function execution and property access from the evaluation context. Evaluating expressions from untrusted users is inherently unsafe and may lead to arbitrary code execution.

Security Best Practices:

  1. Use Pure Contexts: Create contexts with Object.create(null) to prevent prototype pollution
  2. Apply Least Privilege: Expose only essential variables/functions in contexts
  3. Never Evaluate Untrusted Input: Restrict usage to controlled environments

⚠️ Bitwise Operation Precision

Bitwise operators (&, |, ~, etc.) are defined for fixed-width integers. When applied to arbitrary-precision numbers (especially non-integers), behavior is implementation-defined.

Implementation Responsibility:

  • You must provide all bitwise operator implementations
  • The BigNumber example demonstrates a pragmatic approach: downgrading to JavaScript Number for bitwise ops
  • Precision is limited to JavaScript integers in this approach

Carefully consider your precision requirements when implementing these operators.

⚠️ Disabling Unsupported Operators

Explicitly block unsupported operators by throwing errors in their implementations:

binaryOperators: {
  // ...supported operators
  "&": () => { throw new Error("Bitwise AND not supported") },
  "|": () => { throw new Error("Bitwise OR not supported") },
},
unaryOperators: {
  "~": () => { throw new Error("Bitwise NOT not supported") },
}

License

MIT License © @AEPKILL