nerdamer-prime
Version:
javascript light-weight symbolic math library
1,684 lines (1,539 loc) • 634 kB
JavaScript
/*
* Author : Martin Donk
* Website : http://www.nerdamer.com
* Email : martin.r.donk@gmail.com
* Source : https://github.com/jiggzson/nerdamer
*/
// Type imports for JSDoc ======================================================
// These typedefs provide type aliases for the interfaces defined in index.d.ts.
// They enable proper type checking when working with the classes defined in this file.
//
// Usage patterns:
// - For return types: @returns {NerdamerSymbolType}
// - For parameters: @param {NerdamerSymbolType} symbol
// - For variable declarations: /** @type {NerdamerSymbolType} */
//
// Note: When casting local class instances to interface types, use the pattern:
// /** @type {InterfaceType} */ (/** @type {unknown} */ (localInstance))
// This is needed because TypeScript sees local classes and interfaces as separate types.
/**
* Core type aliases from index.d.ts
*
* @typedef {import('./index').NerdamerCore.NerdamerSymbol} NerdamerSymbolType
*
* @typedef {import('./index').NerdamerCore.Frac} FracType
*
* @typedef {import('./index').NerdamerCore.Vector} VectorType
*
* @typedef {import('./index').NerdamerCore.Matrix} MatrixType
*
* @typedef {import('./index').NerdamerCore.Parser} ParserType
*
* @typedef {import('./index').NerdamerCore.Collection} CollectionType
*
* @typedef {import('./index').NerdamerCore.NerdamerSet} SetType
*
* @typedef {import('./index').NerdamerCore.Settings} SettingsType
*
* @typedef {import('./index').NerdamerExpression} ExpressionType
*
* @typedef {typeof import('./index')} NerdamerType
*
* @typedef {import('./index').NerdamerCore.Token} TokenType
*
* @typedef {import('./index').NerdamerCore.ScopeArray} ScopeArrayType
*
* Arithmetic operand type (Symbol, Vector, or Matrix)
*
* @typedef {import('./index').ArithmeticOperand} ArithmeticOperand
*
* Expand options type
*
* @typedef {import('./index').ExpandOptions} ExpandOptions
*
* LaTeX token types
*
* @typedef {import('./index').LaTeXToken} LaTeXTokenType
*
* @typedef {import('./index').FilteredLaTeXToken} FilteredLaTeXTokenType
*
* Output and parameter types
*
* @typedef {import('./index').OutputType} OutputType
*
* @typedef {import('./index').ExpressionParam} ExpressionParam
*
* @typedef {import('./index').SortFn<unknown>} SortFn
*
* Constructor types (for factory functions)
*
* @typedef {import('./index').NerdamerCore.FracConstructor} FracConstructor
*
* @typedef {import('./index').NerdamerCore.SymbolConstructor} SymbolConstructor
*
* @typedef {import('./index').NerdamerCore.VectorConstructor} VectorConstructor
*
* @typedef {import('./index').NerdamerCore.MatrixConstructor} MatrixConstructor
*
* @typedef {import('./index').NerdamerCore.ExpressionConstructor} ExpressionConstructor
*
* @typedef {import('./index').NerdamerCore.SetConstructor} SetConstructor
*
* @typedef {import('./index').NerdamerCore.CollectionConstructor} CollectionConstructor
*
* @typedef {import('./index').NerdamerCore.Fraction} FractionInterface
*
* @typedef {import('./index').NerdamerCore.ScientificConstructor} ScientificConstructor
*
* @typedef {import('./index').NerdamerCore.ParserConstructor} ParserConstructor
*
* @typedef {import('./index').NerdamerCore.LaTeX} LaTeXInterface
*
* @typedef {import('./index').NerdamerCore.Math2} Math2Interface
*
* @typedef {import('./index').NerdamerCore.Build} BuildInterface
*
* @typedef {import('./index').NerdamerCore.CoreUtils} CoreUtilsInterface
*
* @typedef {import('./index').NerdamerCore.Utils} UtilsInterface
*
* @typedef {import('./index').NerdamerCore.InternalParseResult} InternalParseResult
*
* Exceptions object type (for CoreDeps.exceptions)
*
* @typedef {{
* DivisionByZero: CustomErrorConstructor;
* ParseError: CustomErrorConstructor;
* OutOfFunctionDomainError: CustomErrorConstructor;
* UndefinedError: CustomErrorConstructor;
* MaximumIterationsReached: CustomErrorConstructor;
* NerdamerTypeError: CustomErrorConstructor;
* ParityError: CustomErrorConstructor;
* OperatorError: CustomErrorConstructor;
* OutOfRangeError: CustomErrorConstructor;
* DimensionError: CustomErrorConstructor;
* InvalidVariableNameError: CustomErrorConstructor;
* ValueLimitExceededError: CustomErrorConstructor;
* NerdamerValueError: CustomErrorConstructor;
* SolveError: CustomErrorConstructor;
* InfiniteLoopError: CustomErrorConstructor;
* UnexpectedTokenError: CustomErrorConstructor;
* }} ExceptionsType
* Exception types
*
* @typedef {import('./index').NerdamerCore.DivisionByZero} DivisionByZeroType
*
* @typedef {import('./index').NerdamerCore.ParseError} ParseErrorType
*
* @typedef {import('./index').NerdamerCore.NerdamerTypeError} NerdamerTypeErrorType
*
* @typedef {import('./index').NerdamerCore.Core} CoreType
*
* @typedef {import('./index').NerdamerCore.PowerValue} PowerValueType
*
* @typedef {import('big-integer').BigInteger} BigIntegerType
*
* @typedef {import('big-integer').BigIntegerStatic} BigIntegerStaticType
*
* @typedef {import('decimal.js').Decimal} DecimalType
*
* @typedef {typeof import('decimal.js').default} DecimalStaticType
*
* Custom error constructor type - used for exception classes
*
* @typedef {new (message?: string) => Error} CustomErrorConstructor
*
* @typedef {Record<
* string,
* [Function, number] | [Function, number[]] | [Function, number, { name: string; params: string[]; body: string }]
* >} FunctionMapType
*/
// externals ====================================================================
/* BigInteger.js v1.6.28 https://github.com/peterolson/BigInteger.js/blob/master/LICENSE */
const nerdamerBigInt =
typeof globalThis.nerdamerBigInt === 'undefined' ? require('big-integer') : globalThis.nerdamerBigInt;
/* Decimal.js v10.2.1 https://github.com/MikeMcl/decimal.js/LICENCE */
const nerdamerBigDecimal =
typeof globalThis.nerdamerBigDecimal === 'undefined' ? require('decimal.js') : globalThis.nerdamerBigDecimal;
// Set BigDecimal precision immediately after import
nerdamerBigDecimal.set({ precision: 250 });
/* Mathematical constants */
const nerdamerConstants =
typeof globalThis.nerdamerConstants === 'undefined' ? require('./constants.js') : globalThis.nerdamerConstants;
// ============================================================================
// Runtime state variables - declared before CoreDeps to avoid forward references
// ============================================================================
// Custom operators registry - populated by IIFE
/** @type {{ [key: string]: { precedence: number; operator: string; action: string; postfix?: boolean } }} */
const CUSTOM_OPERATORS = {};
// Runtime state arrays - used by CoreDeps.state getters
/** @type {ExpressionType[]} */
const EXPRESSIONS = [];
/** @type {Record<string, NerdamerSymbolType>} */
const VARS_STORE = {};
/** @type {string[]} */
const RESERVED = [];
/** @type {string[]} */
const WARNINGS = [];
/** @type {string[]} */
const USER_FUNCTIONS = [];
// Late-binding references container - populated after classes are defined
// Used by CoreDeps getters to avoid forward reference issues
/**
* @type {{
* Settings: SettingsType | null;
* Math2: Math2Interface | null;
* }}
*/
const LateRefs = {
Settings: /** @type {SettingsType | null} */ (null),
Math2: /** @type {Math2Interface | null} */ (null),
};
// CoreDeps - Centralized Dependency Registry ==================================
// This single registry replaces 45+ scattered *Deps objects with a unified,
// hierarchical structure. Benefits:
// - Single source of truth for all shared dependencies
// - Clear initialization order (externals -> constants -> classes -> parser)
// - Lazy getters for values defined later in initialization
// - Type-safe access patterns
//
// Structure:
// CoreDeps.ext - External imports (bigInt, bigDec, constants)
// CoreDeps.groups - Symbol group constants (N, P, S, EX, FN, PL, CB, CP)
// CoreDeps.fnNames - Function name constants (SQRT, ABS, FACTORIAL, etc.)
// CoreDeps.settings - Settings reference
// CoreDeps.state - Runtime state (EXPRESSIONS, VARS, RESERVED, etc.)
// CoreDeps.classes - Class constructors (Frac, NerdamerSymbol, Vector, etc.)
// CoreDeps.utils - Utility functions
// CoreDeps.parser - Parser instance (set during IIFE init)
// CoreDeps.core - Core object C (set during IIFE init)
/**
* @type {{
* ext: {
* bigInt: BigIntegerStaticType;
* bigDec: DecimalStaticType;
* PRIMES: number[];
* PRIMES_SET: Record<number, boolean>;
* LONG_PI: string;
* LONG_E: string;
* BIG_LOG_CACHE: string[];
* };
* groups: {
* N: 1;
* P: 2;
* S: 3;
* EX: 4;
* FN: 5;
* PL: 6;
* CB: 7;
* CP: 8;
* };
* fnNames: {
* SQRT: 'sqrt';
* ABS: 'abs';
* FACTORIAL: 'factorial';
* DOUBLEFACTORIAL: 'dfactorial';
* PARENTHESIS: 'parens';
* LOG: 'log';
* CONST_HASH: '#';
* };
* settings: SettingsType;
* state: {
* EXPRESSIONS: ExpressionType[];
* VARS: Record<string, NerdamerSymbolType>;
* CONSTANTS: Record<string, NerdamerSymbolType | string | number>;
* RESERVED: string[];
* WARNINGS: string[];
* USER_FUNCTIONS: string[];
* CUSTOM_OPERATORS: {
* [key: string]: { precedence: number; operator: string; action: string; postfix?: boolean };
* };
* };
* classes: {
* Frac: FracConstructor;
* Fraction: FractionInterface;
* NerdamerSymbol: SymbolConstructor;
* Vector: VectorConstructor;
* Matrix: MatrixConstructor;
* Expression: ExpressionConstructor;
* Collection: CollectionConstructor;
* NerdamerSet: SetConstructor;
* Scientific: ScientificConstructor;
* Parser: ParserConstructor;
* LaTeX: LaTeXInterface;
* Math2: Math2Interface;
* Build: BuildInterface;
* };
* utils: {
* isSymbol: (x: unknown) => boolean;
* isVector: (x: unknown) => boolean;
* isMatrix: (x: unknown) => boolean;
* isExpression: (x: unknown) => boolean;
* isNumericSymbol: (symbol: NerdamerSymbolType) => boolean;
* isFraction: (x: unknown) => boolean;
* isArray: (arr: unknown) => boolean;
* isInt: (n: number | string | unknown) => boolean;
* text: (symbol: NerdamerSymbolType, opt?: OutputType, useGroup?: number, decp?: number) => string;
* variables: (obj: NerdamerSymbolType | FracType, poly?: boolean, vars?: unknown) => string[];
* scientificToDecimal: (num: number) => string;
* err: (msg: string, ErrorObj?: CustomErrorConstructor) => void;
* block: (setting: string, f: Function, opt?: boolean, obj?: unknown) => unknown;
* evaluate: (symbol: NerdamerSymbolType, o?: Record<string, ExpressionParam>) => NerdamerSymbolType;
* reserveNames: (obj: object) => void;
* nround: (x: string | number, s?: number) => string | number;
* remove: (arr: unknown[], index: number) => unknown;
* _setFunction: (fnName: string | Function, fnParams?: string[], fnBody?: string) => boolean;
* _clearFunctions: () => void;
* symfunction: (fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType;
* callfunction: (fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType;
* };
* exceptions: ExceptionsType;
* parser: ParserType;
* core: CoreType;
* libExports: typeof nerdamer;
* version: string;
* }}
*/
const CoreDeps = {
// External imports - available immediately
ext: {
bigInt: nerdamerBigInt,
bigDec: nerdamerBigDecimal,
PRIMES: nerdamerConstants.PRIMES,
PRIMES_SET: nerdamerConstants.PRIMES_SET,
LONG_PI: nerdamerConstants.LONG_PI,
LONG_E: nerdamerConstants.LONG_E,
BIG_LOG_CACHE: nerdamerConstants.BIG_LOG_CACHE,
},
// Symbol group constants - available immediately
groups: {
N: 1, // A number
P: 2, // A number with a rational power e.g. 2^(3/5)
S: 3, // A single variable e.g. x
EX: 4, // An exponential
FN: 5, // A function
PL: 6, // Same name, different powers e.g. 1/x + x^2
CB: 7, // Multiplication composite e.g. x*y
CP: 8, // Addition composite e.g. x+1 or x+y
},
// Function name constants - available immediately
fnNames: {
SQRT: 'sqrt',
ABS: 'abs',
FACTORIAL: 'factorial',
DOUBLEFACTORIAL: 'dfactorial',
PARENTHESIS: 'parens',
LOG: 'log',
CONST_HASH: '#',
},
// Settings reference - getter using LateRefs for forward reference safety
get settings() {
return LateRefs.Settings;
},
// Runtime state - arrays now defined before CoreDeps
state: {
get EXPRESSIONS() {
return EXPRESSIONS;
},
get VARS() {
return VARS_STORE;
},
CONSTANTS: /** @type {Record<string, NerdamerSymbolType | string>} */ ({}),
get RESERVED() {
return RESERVED;
},
get WARNINGS() {
return WARNINGS;
},
get USER_FUNCTIONS() {
return USER_FUNCTIONS;
},
get CUSTOM_OPERATORS() {
return CUSTOM_OPERATORS;
},
},
// Class constructors - set by IIFE after class definitions
classes: {
Frac: /** @type {FracConstructor} */ (null),
Fraction: /** @type {FractionInterface} */ (null),
NerdamerSymbol: /** @type {SymbolConstructor} */ (null),
Vector: /** @type {VectorConstructor} */ (null),
Matrix: /** @type {MatrixConstructor} */ (null),
Expression: /** @type {ExpressionConstructor} */ (null),
Collection: /** @type {CollectionConstructor} */ (null),
NerdamerSet: /** @type {SetConstructor} */ (null),
Scientific: /** @type {ScientificConstructor} */ (null),
Parser: /** @type {ParserConstructor} */ (null),
LaTeX: /** @type {LaTeXInterface} */ (null),
Math2: /** @type {Math2Interface} */ (null),
Build: /** @type {BuildInterface} */ (null),
},
// Utility functions - use getters for module-scope functions
// symfunction and callfunction are set by IIFE since they need parser binding
utils: {
get isSymbol() {
return isSymbol;
},
get isVector() {
return isVector;
},
get isMatrix() {
return isMatrix;
},
get isExpression() {
return isExpression;
},
get isNumericSymbol() {
return isNumericSymbol;
},
get isFraction() {
return isFraction;
},
get isArray() {
return isArray;
},
get isInt() {
return isInt;
},
get text() {
return text;
},
get variables() {
return variables;
},
get scientificToDecimal() {
return scientificToDecimal;
},
get err() {
return err;
},
get block() {
return block;
},
get evaluate() {
return evaluate;
},
get reserveNames() {
return reserveNames;
},
get nround() {
return nround;
},
get remove() {
return remove;
},
get _setFunction() {
return _setFunction;
},
get _clearFunctions() {
return _clearFunctions;
},
// Parser-bound methods - set by IIFE after parser instantiation
symfunction: /** @type {(fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType} */ (null),
callfunction: /** @type {(fname: string, args: NerdamerSymbolType[]) => NerdamerSymbolType} */ (null),
},
// Exception classes - assigned after exception definitions (see below Frac class)
exceptions: /** @type {ExceptionsType} */ (null),
// Parser instance - set by IIFE after Parser creation
parser: /** @type {ParserType} */ (null),
// Core object C - set by IIFE at end
core: /** @type {CoreType} */ (null),
// Library exports function - set by IIFE
libExports: /** @type {typeof nerdamer} */ (null),
// Version string
version: '1.1.16',
};
// Groups object - maps to CoreDeps.groups for external access
const Groups = {
N: CoreDeps.groups.N,
P: CoreDeps.groups.P,
S: CoreDeps.groups.S,
EX: CoreDeps.groups.EX,
FN: CoreDeps.groups.FN,
PL: CoreDeps.groups.PL,
CB: CoreDeps.groups.CB,
CP: CoreDeps.groups.CP,
};
// ============================================================================
// Math Polyfills
// ============================================================================
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/
Math.sign ||= function sign(x) {
x = Number(x); // Convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};
Math.cosh ||= function cosh(x) {
const y = Math.exp(x);
return (y + 1 / y) / 2;
};
Math.sech ||= function sech(x) {
return 1 / Math.cosh(x);
};
Math.csch ||= function csch(x) {
return 1 / Math.sinh(x);
};
Math.coth ||= function coth(x) {
return 1 / Math.tanh(x);
};
Math.sinh ||= function sinh(x) {
const y = Math.exp(x);
return (y - 1 / y) / 2;
};
Math.tanh ||= function tanh(x) {
if (x === Infinity) {
return 1;
}
if (x === -Infinity) {
return -1;
}
const y = Math.exp(2 * x);
return (y - 1) / (y + 1);
};
Math.asinh ||= function asinh(x) {
if (x === -Infinity) {
return x;
}
return Math.log(x + Math.sqrt(x * x + 1));
};
Math.acosh ||= function acosh(x) {
return Math.log(x + Math.sqrt(x * x - 1));
};
Math.atanh ||= function atanh(x) {
return Math.log((1 + x) / (1 - x)) / 2;
};
Math.trunc ||= function trunc(x) {
if (isNaN(x)) {
return NaN;
}
if (x > 0) {
return Math.floor(x);
}
return Math.ceil(x);
};
// ============================================================================
// Scientific notation helper
// ============================================================================
// Extracted as standalone function to avoid forward-reference to Scientific class
/**
* Checks if a string is in scientific notation (e.g., "1.5e10", "2E-5")
*
* @param {string} num
* @returns {boolean}
*/
function isScientificNotation(num) {
return /\d+\.?\d*e[+-]*\d+/iu.test(num);
}
// Fraction Object ==============================================================
// Extracted outside IIFE to enable proper TypeScript type inference.
// This static utility object converts decimals to fractions.
/** Static utility object for converting decimals to fractions. */
const Fraction = {
/**
* Converts a decimal to a fraction
*
* @param {number | string} value
* @param {object} [_opts]
* @returns {Array} An array containing the numerator and the denominator
*/
convert(value, _opts) {
const numValue = Number(value);
let frac;
if (numValue === 0) {
frac = [0, 1];
} else if (Math.abs(numValue) < 1e-6 || Math.abs(numValue) > 1e20) {
const qc = this.quickConversion(numValue);
if (qc[1] <= 1e16) {
const abs = Math.abs(numValue);
const sign = numValue / abs;
frac = this.fullConversion(abs.toFixed(`${qc[1]}`.length - 1));
frac[0] *= sign;
} else {
frac = qc;
}
} else {
frac = this.fullConversion(numValue);
}
return frac;
},
/**
* If the fraction is too small or too large this gets called instead of fullConversion method
*
* @param {number | string} value
* @returns {Array} An array containing the numerator and the denominator as strings
*/
quickConversion(value) {
const stripSign = function (s) {
// Explicitely convert to a string
if (typeof s !== 'string') {
s = s.toString();
}
let sign = '';
// Remove and store the sign
const start = s.charAt(0);
if (start === '-') {
s = s.substr(1, s.length);
sign = '-';
} else if (start === '+') {
// Just remove the plus sign
s = s.substr(1, s.length);
}
return {
sign,
value: s,
};
};
function convert(val) {
// Explicitely convert to a decimal
if (isScientificNotation(val)) {
val = scientificToDecimal(val);
}
// Split the value into the sign and the value
const nparts = stripSign(val);
// Split it at the decimal. We'll refer to it as the coeffient parts
const cparts = nparts.value.split('.');
// Combine the entire number by removing leading zero and adding the decimal part
// This would be teh same as moving the decimal point to the end
let num;
// We're dealing with integers
if (cparts.length === 1) {
num = cparts[0];
} else {
num = cparts[0] + cparts[1];
}
const n = cparts[1] ? cparts[1].length : 0;
// Generate the padding for the zeros
const den = `1${'0'.repeat(n)}`;
if (num !== '0') {
num = num.replace(/^0+/u, '');
}
return [nparts.sign + num, den];
}
return convert(value);
},
/**
* Returns a good approximation of a fraction. This method gets called by convert
* http://mathforum.org/library/drmath/view/61772.html Decimal To Fraction Conversion - A Simpler Version Dr
* Peterson
*
* @param {number | string} dec
* @returns {Array} An array containing the numerator and the denominator
*/
fullConversion(dec) {
const numDec = Number(dec);
// This doesn't work for values approaching as small as epsilon
const epsilon = Math.abs(numDec) > 1e10 ? 1e-16 : 1e-30;
let done = false;
// You can adjust the epsilon to a larger number if you don't need very high precision
let n1 = 0;
let d1 = 1;
let n2 = 1;
let d2 = 0;
let n = 0;
let q = numDec;
let num;
let den;
// Relative epsilon for rounding large q values to nearest integer.
// This fixes floating-point precision errors in reciprocals (e.g., 1/1e-15 = 999999999999999.9).
// We use ~45x Number.EPSILON to allow for accumulated rounding errors.
// This is independent of Settings.PRECISION since we're dealing with IEEE 754 double limits.
const roundingEpsilon = 1e-14; // ~45 * Number.EPSILON (2.2e-16)
while (!done) {
n++;
// For very large q values, round to nearest integer if within floating-point error
let a;
if (Math.abs(q) > 1e10) {
const rounded = Math.round(q);
const relDiff = Math.abs(q - rounded) / Math.abs(rounded);
a = relDiff < roundingEpsilon ? rounded : Math.floor(q);
} else {
a = Math.floor(q);
}
num = n1 + a * n2;
den = d1 + a * d2;
const e = q - a;
if (e < epsilon) {
done = true;
}
q = 1 / e;
n1 = n2;
d1 = d2;
n2 = num;
d2 = den;
if (Math.abs(num / den - numDec) < epsilon || n > 30) {
done = true;
}
}
return [num, den];
},
};
// Assign Fraction to CoreDeps immediately
CoreDeps.classes.Fraction = /** @type {FractionInterface} */ (/** @type {unknown} */ (Fraction));
// CustomError Function =============================================================
// Extracted outside IIFE to enable proper TypeScript type inference.
// This function creates custom error classes.
/**
* Creates a custom error class with the given name.
*
* @param {string} name - The name of the custom error class
* @returns {new (message?: string) => Error} A custom error constructor
*/
function customError(name) {
const E = function (message) {
this.name = name;
this.message = message === undefined ? '' : message;
const error = new Error(this.message);
error.name = this.name;
this.stack = error.stack;
}; // Create an empty error
E.prototype = Object.create(Error.prototype);
return E;
}
// DivisionByZero Error ================================================================
/**
* Error thrown for division by zero.
*
* @type {new (message?: string) => Error}
*/
const DivisionByZero = customError('DivisionByZero');
// ParseError Error ====================================================================
/**
* Error thrown if an error occurred during parsing.
*
* @type {new (message?: string) => Error}
*/
const ParseError = customError('ParseError');
// UndefinedError Error ================================================================
/**
* Error thrown if the expression results in undefined.
*
* @type {new (message?: string) => Error}
*/
const UndefinedError = customError('UndefinedError');
// OutOfFunctionDomainError Error ======================================================
/**
* Error thrown if input is out of the function domain.
*
* @type {new (message?: string) => Error}
*/
const OutOfFunctionDomainError = customError('OutOfFunctionDomainError');
// MaximumIterationsReached Error ======================================================
/**
* Error thrown if a function exceeds maximum iterations.
*
* @type {new (message?: string) => Error}
*/
const MaximumIterationsReached = customError('MaximumIterationsReached');
// NerdamerTypeError Error =============================================================
/**
* Error thrown if the parser receives an incorrect type.
*
* @type {new (message?: string) => Error}
*/
const NerdamerTypeError = customError('NerdamerTypeError');
// ParityError Error ===================================================================
/**
* Error thrown if bracket parity is not correct.
*
* @type {new (message?: string) => Error}
*/
const ParityError = customError('ParityError');
// OperatorError Error =================================================================
/**
* Error thrown if an unexpected or incorrect operator is encountered.
*
* @type {new (message?: string) => Error}
*/
const OperatorError = customError('OperatorError');
// OutOfRangeError Error ===============================================================
/**
* Error thrown if an index is out of range.
*
* @type {new (message?: string) => Error}
*/
const OutOfRangeError = customError('OutOfRangeError');
// DimensionError Error ================================================================
/**
* Error thrown if dimensions are incorrect (mostly for matrices).
*
* @type {new (message?: string) => Error}
*/
const DimensionError = customError('DimensionError');
// InvalidVariableNameError Error ======================================================
/**
* Error thrown if variable name violates naming rule.
*
* @type {new (message?: string) => Error}
*/
const InvalidVariableNameError = customError('InvalidVariableNameError');
// ValueLimitExceededError Error =======================================================
/**
* Error thrown if the limits of the library are exceeded for a function.
*
* @type {new (message?: string) => Error}
*/
const ValueLimitExceededError = customError('ValueLimitExceededError');
// NerdamerValueError Error ============================================================
/**
* Error thrown if the value is an incorrect LH or RH value.
*
* @type {new (message?: string) => Error}
*/
const NerdamerValueError = customError('NerdamerValueError');
// SolveError Error ====================================================================
/**
* Error thrown for solve-related errors.
*
* @type {new (message?: string) => Error}
*/
const SolveError = customError('SolveError');
// InfiniteLoopError Error =============================================================
/**
* Error thrown for an infinite loop.
*
* @type {new (message?: string) => Error}
*/
const InfiniteLoopError = customError('InfiniteLoopError');
// UnexpectedTokenError Error ==========================================================
/**
* Error thrown if an operator is found when there shouldn't be one.
*
* @type {new (message?: string) => Error}
*/
const UnexpectedTokenError = customError('UnexpectedTokenError');
// Assign CoreDeps.exceptions now that all exception classes are defined
CoreDeps.exceptions = {
DivisionByZero,
ParseError,
OutOfFunctionDomainError,
UndefinedError,
MaximumIterationsReached,
NerdamerTypeError,
ParityError,
OperatorError,
OutOfRangeError,
DimensionError,
InvalidVariableNameError,
ValueLimitExceededError,
NerdamerValueError,
SolveError,
InfiniteLoopError,
UnexpectedTokenError,
};
// Frac Class ===================================================================
// Extracted outside IIFE to enable proper TypeScript type inference.
// Dependencies are accessed via CoreDeps for centralized management.
/**
* Dependency accessor for Frac class. Uses CoreDeps as the single source of truth for all dependencies.
*
* Note: bigInt and bigDec are typed as BigIntegerStaticType and DecimalStaticType. While these types don't expose
* constructor signatures in TypeScript, the libraries support 'new' at runtime. Type assertions are used at call
* sites.
*
* @type {{
* bigInt: BigIntegerStaticType;
* bigDec: DecimalStaticType;
* isInt: (n: number | string | unknown) => boolean;
* scientificToDecimal: (num: number) => string;
* DivisionByZero: CustomErrorConstructor;
* Settings: SettingsType;
* Fraction: typeof Fraction;
* }}
*/
const FracDeps = {
get bigInt() {
return CoreDeps.ext.bigInt;
},
get bigDec() {
return CoreDeps.ext.bigDec;
},
get isInt() {
return CoreDeps.utils.isInt;
},
get scientificToDecimal() {
return CoreDeps.utils.scientificToDecimal;
},
get DivisionByZero() {
return DivisionByZero;
},
get Settings() {
return CoreDeps.settings;
},
get Fraction() {
return Fraction;
},
};
/**
* High-precision fraction class.
*
* @implements {FracType}
*/
class Frac {
/** @type {BigIntegerType} */
num;
/** @type {BigIntegerType} */
den;
/** @param {number | string | Frac} [n] */
constructor(n) {
if (n instanceof Frac) {
// eslint-disable-next-line no-constructor-return -- Frac is designed to return existing instances
return n;
}
if (n === undefined) {
// eslint-disable-next-line no-constructor-return -- Early return for undefined
return this;
}
try {
if (FracDeps.isInt(n)) {
try {
// @ts-expect-error - bigInt accepts string | number at runtime
this.num = FracDeps.bigInt(n);
this.den = FracDeps.bigInt(1);
} catch (e) {
if (/** @type {Error} */ (e).message === 'timeout') {
throw e;
}
// eslint-disable-next-line no-constructor-return -- Fallback to simple parsing
return Frac.simple(n);
}
} else {
const frac =
/** @type {unknown} */ (n) instanceof FracDeps.bigDec
? FracDeps.Fraction.quickConversion(n)
: FracDeps.Fraction.convert(n);
// @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
this.num = new FracDeps.bigInt(frac[0]);
// @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
this.den = new FracDeps.bigInt(frac[1]);
}
} catch (e) {
if (/** @type {Error} */ (e).message === 'timeout') {
throw e;
}
// eslint-disable-next-line no-constructor-return -- Fallback to simple parsing
return Frac.simple(n);
}
}
/**
* Safe to use with negative numbers or other types
*
* @param {number | string | FracType} n
* @returns {FracType}
*/
static create(n) {
if (n instanceof Frac) {
return n;
}
n = n.toString();
const isNeg = n.charAt(0) === '-';
if (isNeg) {
n = n.substr(1, n.length - 1);
}
const frac = new Frac(n);
if (isNeg) {
frac.negate();
}
return frac;
}
/**
* @param {unknown} o
* @returns {o is FracType}
*/
static isFrac(o) {
return o instanceof Frac;
}
/**
* @param {string | number} n
* @param {string | number} d
* @returns {FracType}
*/
static quick(n, d) {
const frac = new Frac();
// @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
frac.num = new FracDeps.bigInt(n);
// @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
frac.den = new FracDeps.bigInt(d);
return frac;
}
/**
* @param {number | string} n
* @returns {FracType}
*/
static simple(n) {
const nstr = String(FracDeps.scientificToDecimal(/** @type {number} */ (n)));
const mDc = nstr.split('.');
const num = mDc.join('');
/** @type {string} */
let den = '1';
const l = (mDc[1] || '').length;
for (let i = 0; i < l; i++) {
den += '0';
}
const frac = Frac.quick(num, den);
return frac.simplify();
}
/**
* @param {FracType} m
* @returns {FracType}
*/
multiply(m) {
if (this.isOne()) {
return m.clone();
}
if (m.isOne()) {
return this.clone();
}
const c = this.clone();
c.num = c.num.multiply(m.num);
c.den = c.den.multiply(m.den);
return c.simplify();
}
/**
* @param {FracType} m
* @returns {FracType}
*/
divide(m) {
if (m.equals(0)) {
throw new FracDeps.DivisionByZero('Division by zero not allowed!');
}
return this.clone().multiply(m.clone().invert()).simplify();
}
/**
* @param {FracType} m
* @returns {FracType}
*/
subtract(m) {
return this.clone().add(m.clone().neg());
}
/**
* Alias for subtract
*
* @param {FracType} m
* @returns {FracType}
*/
sub(m) {
return this.subtract(m);
}
/** @returns {this} */
neg() {
this.num = this.num.multiply(-1);
return this;
}
/**
* @param {FracType} m
* @returns {FracType}
*/
add(m) {
const n1 = this.den;
const n2 = m.den;
const c = this.clone();
const a = c.num;
const b = m.num;
if (n1.equals(n2)) {
c.num = a.add(b);
} else {
c.num = a.multiply(n2).add(b.multiply(n1));
c.den = n1.multiply(n2);
}
return c.simplify();
}
/**
* @param {FracType} m
* @returns {FracType}
*/
mod(m) {
const a = this.clone();
const b = m.clone();
a.num = a.num.multiply(b.den);
a.den = a.den.multiply(b.den);
b.num = b.num.multiply(this.den);
b.den = b.den.multiply(this.den);
a.num = a.num.mod(b.num);
return a.simplify();
}
/** @returns {this} */
simplify() {
const gcd = FracDeps.bigInt.gcd(this.num, this.den);
this.num = this.num.divide(gcd);
this.den = this.den.divide(gcd);
return this;
}
/** @returns {FracType} */
clone() {
const m = new Frac();
// @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
m.num = new FracDeps.bigInt(this.num);
// @ts-expect-error - bigInt supports constructor at runtime but not in TypeScript types
m.den = new FracDeps.bigInt(this.den);
return m;
}
/**
* @param {number} [prec]
* @returns {string}
*/
decimal(prec) {
const sign = this.num.isNegative() ? '-' : '';
if (this.num.equals(this.den)) {
return '1';
}
prec ||= FracDeps.Settings.PRECISION;
prec += 2;
const narr = [];
let n = this.num.abs();
const d = this.den;
let i;
for (i = 0; i < prec; i++) {
const w = n.divide(d);
const r = n.subtract(w.multiply(d));
narr.push(w);
if (r.equals(0)) {
break;
}
n = r.times(10);
}
const whole = narr.shift();
if (narr.length === 0) {
return sign + whole.toString();
}
if (i === prec) {
const lt = [];
for (let j = 0; j < 2; j++) {
lt.unshift(narr.pop());
}
narr.push(Math.round(Number(lt.join('.'))));
}
const dec = `${whole.toString()}.${narr.join('')}`;
return sign + dec;
}
/**
* @param {number} [prec]
* @returns {string | number}
*/
toDecimal(prec) {
prec ||= FracDeps.Settings.PRECISION;
if (prec) {
return this.decimal(prec);
}
return this.num.valueOf() / this.den.valueOf();
}
/**
* @param {FracType} n
* @returns {[BigIntegerType, BigIntegerType]}
*/
qcompare(n) {
return [this.num.multiply(n.den), n.num.multiply(this.den)];
}
/**
* @param {number | FracType} n
* @returns {boolean}
*/
equals(n) {
if (!isNaN(/** @type {number} */ (n))) {
n = new Frac(/** @type {number} */ (n));
}
const q = this.qcompare(/** @type {FracType} */ (n));
return q[0].equals(q[1]);
}
/**
* @param {number | FracType} n
* @returns {boolean}
*/
absEquals(n) {
if (!isNaN(/** @type {number} */ (n))) {
n = new Frac(/** @type {number} */ (n));
}
const q = this.qcompare(/** @type {FracType} */ (n));
return q[0].abs().equals(q[1]);
}
/**
* @param {number | FracType} n
* @returns {boolean}
*/
greaterThan(n) {
if (!isNaN(/** @type {number} */ (n))) {
n = new Frac(/** @type {number} */ (n));
}
const q = this.qcompare(/** @type {FracType} */ (n));
return q[0].gt(q[1]);
}
/**
* Alias for greaterThan
*
* @param {number | FracType} n
* @returns {boolean}
*/
gt(n) {
return this.greaterThan(n);
}
/**
* @param {number | FracType} n
* @returns {boolean}
*/
gte(n) {
return this.greaterThan(n) || this.equals(n);
}
/**
* @param {number | FracType} n
* @returns {boolean}
*/
lte(n) {
return this.lessThan(n) || this.equals(n);
}
/**
* @param {number | FracType} n
* @returns {boolean}
*/
lessThan(n) {
if (!isNaN(/** @type {number} */ (n))) {
n = new Frac(/** @type {number} */ (n));
}
const q = this.qcompare(/** @type {FracType} */ (n));
return q[0].lt(q[1]);
}
/**
* Alias for lessThan
*
* @param {number | FracType} n
* @returns {boolean}
*/
lt(n) {
return this.lessThan(n);
}
/** @returns {boolean} */
isInteger() {
return this.den.equals(1);
}
/** @returns {this} */
negate() {
this.num = this.num.multiply(-1);
return this;
}
/** @returns {this} */
invert() {
const t = this.den;
if (!this.num.equals(0)) {
const isnegative = this.num.isNegative();
this.den = this.num.abs();
this.num = t;
if (isnegative) {
this.num = this.num.multiply(-1);
}
}
return this;
}
/** @returns {boolean} */
isOne() {
return this.num.equals(1) && this.den.equals(1);
}
/** @returns {-1 | 1} */
sign() {
return this.num.isNegative() ? -1 : 1;
}
/** @returns {this} */
abs() {
this.num = this.num.abs();
return this;
}
/**
* @param {FracType} f
* @returns {FracType}
*/
gcd(f) {
// @ts-expect-error - bigInt.gcd accepts BigInteger at runtime
return Frac.quick(FracDeps.bigInt.gcd(f.num, this.num), FracDeps.bigInt.lcm(f.den, this.den));
}
/** @returns {string} */
toString() {
return this.den.equals(1) ? this.num.toString() : `${this.num.toString()}/${this.den.toString()}`;
}
/** @returns {number | DecimalType} */
valueOf() {
if (FracDeps.Settings.USE_BIG) {
return new FracDeps.bigDec(this.num.toString()).div(new FracDeps.bigDec(this.den.toString()));
}
const retval = this.num.valueOf() / this.den.valueOf();
return retval;
}
/** @returns {boolean} */
isNegative() {
return /** @type {number} */ (this.toDecimal()) < 0;
}
/**
* Checks if this fraction contains the given number (i.e., is divisible by it)
*
* @param {number | FracType} n
* @returns {boolean}
*/
contains(n) {
const fracN = typeof n === 'number' ? new Frac(n) : n;
return this.mod(fracN).equals(0);
}
}
// Assign Frac to CoreDeps immediately for early access
CoreDeps.classes.Frac = Frac;
// NerdamerSet Class ====================================================================
// Extracted outside IIFE to enable proper TypeScript type inference.
// Dependencies are accessed via CoreDeps for centralized management.
/**
* Dependency accessor for NerdamerSet class. Uses CoreDeps as the single source of truth.
*
* @type {{
* isVector: (x: unknown) => boolean;
* Vector: VectorConstructor;
* remove: (arr: unknown[], index: number) => unknown;
* }}
*/
const SetDeps = {
get isVector() {
return CoreDeps.utils.isVector;
},
get Vector() {
return CoreDeps.classes.Vector;
},
get remove() {
return remove;
}, // Remove is defined later in file
};
/**
* NerdamerSet class for mathematical set operations.
*
* @implements {SetType}
*/
class NerdamerSet {
/** @type {NerdamerSymbolType[]} */
elements = [];
/**
* @param {VectorType | NerdamerSymbolType | undefined} [setArg]
* @param {...NerdamerSymbolType} rest
*/
constructor(setArg, ...rest) {
// If the first object isn't an array, convert it to one.
if (typeof setArg === 'undefined') {
// No arguments passed
return;
}
let setVal = /** @type {VectorType} */ (setArg);
if (!SetDeps.isVector(setArg)) {
setVal = SetDeps.Vector.fromArray([/** @type {NerdamerSymbolType} */ (setArg), ...rest]);
}
if (setVal) {
const { elements } = setVal;
for (let i = 0, l = elements.length; i < l; i++) {
this.add(/** @type {NerdamerSymbolType} */ (elements[i]));
}
}
}
/**
* @param {NerdamerSymbolType[]} arr
* @returns {SetType}
*/
static fromArray(arr) {
const newSet = new NerdamerSet();
for (const item of arr) {
newSet.add(item);
}
return newSet;
}
/** @param {NerdamerSymbolType} x */
add(x) {
if (!this.contains(x)) {
this.elements.push(x.clone());
}
}
/**
* @param {NerdamerSymbolType} x
* @returns {boolean}
*/
contains(x) {
for (let i = 0; i < this.elements.length; i++) {
const e = this.elements[i];
if (x.equals(e)) {
return true;
}
}
return false;
}
/**
* @param {(e: NerdamerSymbolType, inputSet: SetType, i: number) => void} f
* @returns {SetType}
*/
each(f) {
const { elements } = this;
const newSet = new NerdamerSet();
for (let i = 0, l = elements.length; i < l; i++) {
const e = elements[i];
f.call(this, e, newSet, i);
}
return newSet;
}
/** @returns {SetType} */
clone() {
const newSet = new NerdamerSet();
this.each(e => {
newSet.add(e.clone());
});
return newSet;
}
/**
* @param {SetType} inputSet
* @returns {SetType}
*/
union(inputSet) {
const _union = this.clone();
inputSet.each(e => {
_union.add(e);
});
return _union;
}
/**
* @param {SetType} inputSet
* @returns {SetType}
*/
difference(inputSet) {
const diff = this.clone();
inputSet.each(e => {
diff.remove(e);
});
return diff;
}
/**
* @param {NerdamerSymbolType} element
* @returns {boolean}
*/
remove(element) {
for (let i = 0, l = this.elements.length; i < l; i++) {
const e = this.elements[i];
if (e.equals(element)) {
SetDeps.remove(this.elements, i);
return true;
}
}
return false;
}
/**
* @param {SetType} inputSet
* @returns {SetType}
*/
intersection(inputSet) {
const _intersection = new NerdamerSet();
const A = this;
inputSet.each(e => {
if (A.contains(e)) {
_intersection.add(e);
}
});
return _intersection;
}
/**
* @param {SetType} inputSet
* @returns {boolean}
*/
intersects(inputSet) {
return this.intersection(inputSet).elements.length > 0;
}
/**
* @param {SetType} inputSet
* @returns {boolean}
*/
isSubset(inputSet) {
const { elements } = inputSet;
for (let i = 0, l = elements.length; i < l; i++) {
if (!this.contains(elements[i])) {
return false;
}
}
return true;
}
/** @returns {string} */
toString() {
return `{${this.elements.join(',')}}`;
}
}
// Assign NerdamerSet to CoreDeps immediately
CoreDeps.classes.NerdamerSet = NerdamerSet;
// Collection Class =================================================================
// Extracted outside IIFE to enable proper TypeScript type inference.
// Dependencies are injected via CollectionDeps which is set by the IIFE after initialization.
/**
* Dependency container for Collection class. Populated by the IIFE during initialization.
*
* @type {{
* _: ParserType;
* block: (setting: string, f: Function, opt?: boolean, obj?: unknown) => unknown;
* }}
*/
const CollectionDeps = {
get _() {
return CoreDeps.parser;
},
get block() {
return CoreDeps.utils.block;
},
};
/**
* Class used to collect arguments for functions
*
* @implements {CollectionType}
*/
class Collection {
/** @type {NerdamerSymbolType[]} */
elements = [];
/** @param {NerdamerSymbolType} [e] */
constructor(e) {
if (e) {
this.elements.push(e);
}
}
/**
* @param {NerdamerSymbolType} [e]
* @returns {CollectionType}
*/
static create(e) {
return new Collection(e);
}
/** @param {NerdamerSymbolType} e */
append(e) {
this.elements.push(e);
}
/** @returns {NerdamerSymbolType[]} */
getItems() {
return this.elements;
}
/** @returns {string} */
toString() {
return CollectionDeps._.prettyPrint(this.elements);
}
/** @returns {number} */
dimensions() {
return this.elements.length;
}
/**
* @param {string} [options]
* @returns {string}
*/
text(options) {
return `(${this.elements.map(e => e.text(options)).join(',')})`;
}
/** @returns {CollectionType} */
clone() {
const c = Collection.create();
c.elements = this.elements.map(e => e.clone());
return c;
}
/**
* @param {ExpandOptions} [options]
* @returns {this}
*/
expand(options) {
this.elements = /** @type {NerdamerSymbolType[]} */ (
this.elements.map(e => CollectionDeps._.expand(e, options))
);
return this;
}
/**
* @param {Record<string, ExpressionParam>} [options]
* @returns {this}
*/
evaluate(options) {