nerdamer
Version:
javascript light-weight symbolic math expression evaluator
1,436 lines (1,308 loc) • 679 kB
JavaScript
/*
* Author : Martin Donk
* Website : http://www.nerdamer.com
* Email : martin.r.donk@gmail.com
* Source : https://github.com/jiggzson/nerdamer
*/
/* global trig, trigh, Infinity, define, arguments2Array, NaN */
//externals ====================================================================
/* BigInterger.js v1.6.40 https://github.com/peterolson/BigInteger.js/blob/master/LICENSE */
//var nerdamerBigInt = typeof nerdamerBigInt !== 'undefined' ? nerdamerBigInt : require("big-integer");
/* big.js v5.2.2 https://github.com/MikeMcl/big.js/LICENCE */
//var nerdamerBigDecimal = typeof nerdamerBigDecimal !== 'undefined' ? nerdamerBigDecimal : require('big.js');
var nerdamer = (function (imports) {
"use strict";
//version ======================================================================
var version = '1.1.13';
//inits ========================================================================
var _ = new Parser(); //nerdamer's parser
//import bigInt
var bigInt = imports.bigInt;
var bigDec = imports.bigDec;
//set the precision to js precision
bigDec.set({
precision: 250
});
var Groups = {};
//container of pregenerated primes
var PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113
, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397,
401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557,
563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701,
709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031,
1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171,
1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303,
1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471,
1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607,
1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753,
1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931,
1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083];
//Settings =====================================================================
var CUSTOM_OPERATORS = {};
var Settings = {
//Enables/Disables call peekers. False means callPeekers are disabled and true means callPeekers are enabled.
callPeekers: false,
//the max number up to which to cache primes. Making this too high causes performance issues
init_primes: 1000,
exclude: [],
//If you don't care about division by zero for example then this can be set to true.
//Has some nasty side effects so choose carefully.
suppress_errors: false,
//the global used to invoke the libary to parse to a number. Normally cos(9) for example returns
//cos(9) for convenience but parse to number will always try to return a number if set to true.
PARSE2NUMBER: false,
//this flag forces the a clone to be returned when add, subtract, etc... is called
SAFE: false,
//the symbol to use for imaginary symbols
IMAGINARY: 'i',
//the modules used to link numeric function holders
FUNCTION_MODULES: [Math],
//Allow certain characters
ALLOW_CHARS: ['π'],
//Allow nerdamer to convert multi-character variables
USE_MULTICHARACTER_VARS: true,
//Allow changing of power operator
POWER_OPERATOR: '^',
//The variable validation regex
//VALIDATION_REGEX: /^[a-z_][a-z\d\_]*$/i
VALIDATION_REGEX: /^[a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ∞][0-9a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ]*$/i,
// The regex used to determine which characters should be included in implied multiplication
IMPLIED_MULTIPLICATION_REGEX: /([\+\-\/\*]*[0-9]+)([a-z_αAβBγΓδΔϵEζZηHθΘιIκKλΛμMνNξΞoOπΠρPσΣτTυϒϕΦχXψΨωΩ]+[\+\-\/\*]*)/gi,
//Aliases
ALIASES: {
'π': 'pi',
'∞': 'Infinity'
},
POSITIVE_MULTIPLIERS: false,
//Cached items
CACHE: {},
//Print out warnings or not
SILENCE_WARNINGS: false,
// Precision
PRECISION: 21,
// The Expression defaults to this value for decimal places
EXPRESSION_DECP: 19,
// The text function defaults to this value for decimal places
DEFAULT_DECP: 16,
//function mappings
VECTOR: 'vector',
PARENTHESIS: 'parens',
SQRT: 'sqrt',
ABS: 'abs',
FACTORIAL: 'factorial',
DOUBLEFACTORIAL: 'dfactorial',
//reference pi and e
LONG_PI: '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214' +
'808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196',
LONG_E: '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466' +
'39193200305992181741359662904357290033429526059563073813232862794349076323382988075319525101901',
PI: Math.PI,
E: Math.E,
LOG: 'log',
LOG10: 'log10',
LOG10_LATEX: 'log_{10}',
MAX_EXP: 200000,
//The number of scientific place to round to
SCIENTIFIC_MAX_DECIMAL_PLACES: 14,
//True if ints should not be converted to
SCIENTIFIC_IGNORE_ZERO_EXPONENTS: true
};
(function () {
Settings.CACHE.roots = {};
var x = 40,
y = 40;
for(var i = 2; i <= x; i++) {
for(var j = 2; j <= y; j++) {
var nthpow = bigInt(i).pow(j);
Settings.CACHE.roots[nthpow + '-' + j] = i;
}
}
})();
//Add the groups. These have been reorganized as of v0.5.1 to make CP the highest group
//The groups that help with organizing during parsing. Note that for FN is still a function even
//when it's raised to a symbol, which typically results in an EX
var N = Groups.N = 1, // A number
P = Groups.P = 2, // A number with a rational power e.g. 2^(3/5).
S = Groups.S = 3, // A single variable e.g. x.
EX = Groups.EX = 4, // An exponential
FN = Groups.FN = 5, // A function
PL = Groups.PL = 6, // A symbol/expression having same name with different powers e.g. 1/x + x^2
CB = Groups.CB = 7, // A symbol/expression composed of one or more variables through multiplication e.g. x*y
CP = Groups.CP = 8; // A symbol/expression composed of one variable and any other symbol or number x+1 or x+y
var CONST_HASH = Settings.CONST_HASH = '#';
var PARENTHESIS = Settings.PARENTHESIS;
var SQRT = Settings.SQRT;
var ABS = Settings.ABS;
var FACTORIAL = Settings.FACTORIAL;
var DOUBLEFACTORIAL = Settings.DOUBLEFACTORIAL;
//the storage container "memory" for parsed expressions
var EXPRESSIONS = [];
//variables
var VARS = {};
//the container used to store all the reserved functions
var RESERVED = [];
var WARNINGS = [];
/**
* Use this when errors are suppressible
* @param {String} msg
* @param {object} ErrorObj
*/
var err = function (msg, ErrorObj) {
if(!Settings.suppress_errors) {
if(ErrorObj)
throw new ErrorObj(msg);
else
throw new Error(msg);
}
};
//Utils ========================================================================
var customError = function (name) {
var E = function (message) {
this.name = name;
this.message = message !== undefined ? message : '';
var 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;
};
/**
* Checks to see if value is one of nerdamer's reserved names
* @param {String} value
* @return boolean
*/
var isReserved = function (value) {
return RESERVED.indexOf(value) !== -1;
};
/**
* Checks to see that all symbols in array are the same
* @param {Symbol[]} arr
* @returns {bool}
*/
var allSame = function (arr) {
var last = arr[0];
for(var i = 1, l = arr.length; i < l; i++)
if(!arr[i].equals(last))
return false;
return true;
};
/**
* Used to pass warnings or low severity errors about the library
* @param msg
*/
var warn = function (msg) {
WARNINGS.push(msg);
if(Settings.SHOW_WARNINGS && console && console.warn) {
console.warn(msg);
}
};
/**
* Enforces rule: "must start with a letter or underscore and
* can have any number of underscores, letters, and numbers thereafter."
* @param name The name of the symbol being checked
* @param {String} typ - The type of symbols that's being validated
* @throws {Exception} - Throws an exception on fail
*/
var validateName = function (name, typ) {
typ = typ || 'variable';
if(Settings.ALLOW_CHARS.indexOf(name) !== -1)
return;
var regex = Settings.VALIDATION_REGEX;
if(!(regex.test(name))) {
throw new InvalidVariableNameError(name + ' is not a valid ' + typ + ' name');
}
};
/**
* Convert number from scientific format to decimal format
* @param {Number} num
*/
var scientificToDecimal = function (num) {
var nsign = Math.sign(num);
//remove the sign
num = Math.abs(num);
//if the number is in scientific notation remove it
if(/\d+\.?\d*e[\+\-]*\d+/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(), //store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e / l,
coeff_array = parts[0].split('.');
if(sign === -1) {
l = l - coeff_array[0].length;
if(l < 0) {
num = coeff_array[0].slice(0, l) + '.' + coeff_array[0].slice(l) + (coeff_array.length === 2 ? coeff_array[1] : '');
}
else {
num = zero + '.' + new Array(l + 1).join(zero) + coeff_array.join('');
}
}
else {
var dec = coeff_array[1];
if(dec)
l = l - dec.length;
if(l < 0) {
num = coeff_array[0] + dec.slice(0, l) + '.' + dec.slice(l);
}
else {
num = coeff_array.join('') + new Array(l + 1).join(zero);
}
}
}
return nsign < 0 ? '-' + num : num;
};
/**
* Checks if number is a prime number
* @param {Number} n - the number to be checked
*/
var isPrime = function (n) {
var q = Math.floor(Math.sqrt(n));
for(var i = 2; i <= q; i++) {
if(n % i === 0)
return false;
}
return true;
};
/**
* Generates an object with known variable value for evaluation
* @param {String} variable
* @param {any} value Any stringifyable object
* @returns {Object}
*/
var knownVariable = function (variable, value) {
var o = {};
o[variable] = value;
return o;
};
/**
* Checks if n is a number
* @param {any} n
*/
var isNumber = function (n) {
return /^\d+\.?\d*$/.test(n);
};
/**
* Checks to see if an array contains only numeric values
* @param {Array} arr
*/
var allNumeric = function (arr) {
for(var i = 0; i < arr.length; i++)
if(!isNumber(arr[i]))
return false;
return true;
};
/**
* Checks to see if a number or Symbol is a fraction
* @param {Number|Symbol} num
* @returns {boolean}
*/
var isFraction = function (num) {
if(isSymbol(num))
return isFraction(num.multiplier.toDecimal());
return (num % 1 !== 0);
};
/**
* Checks to see if the object provided is a Symbol
* @param {Object} obj
*/
var isSymbol = function (obj) {
return (obj instanceof Symbol);
};
/**
* Checks to see if the object provided is an Expression
* @param {Object} obj
*/
var isExpression = function (obj) {
return (obj instanceof Expression);
};
/**
* This method traverses the symbol structure and grabs all the variables in a symbol. The variable
* names are then returned in alphabetical order.
* @param {Symbol} obj
* @param {Boolean} poly
* @param {Object} vars - An object containing the variables. Do not pass this in as it generated
* automatically. In the future this will be a Collector object.
* @returns {String[]} - An array containing variable names
*/
var variables = function (obj, poly, vars) {
vars = vars || {
c: [],
add: function (value) {
if(this.c.indexOf(value) === -1 && isNaN(value))
this.c.push(value);
}
};
if(isSymbol(obj)) {
var group = obj.group,
prevgroup = obj.previousGroup;
if(group === EX)
variables(obj.power, poly, vars);
if(group === CP || group === CB || prevgroup === CP || prevgroup === CB) {
for(var x in obj.symbols) {
variables(obj.symbols[x], poly, vars);
}
}
else if(group === S || prevgroup === S) {
//very crude needs fixing. TODO
if(!(obj.value === 'e' || obj.value === 'pi' || obj.value === Settings.IMAGINARY))
vars.add(obj.value);
}
else if(group === PL || prevgroup === PL) {
variables(firstObject(obj.symbols), poly, vars);
}
else if(group === EX) {
if(!isNaN(obj.value))
vars.add(obj.value);
variables(obj.power, poly, vars);
}
else if(group === FN && !poly) {
for(var i = 0; i < obj.args.length; i++) {
variables(obj.args[i], poly, vars);
}
}
}
return vars.c.sort();
};
/**
* Returns the sum of an array
* @param {Array} arr
* @param {boolean} toNumber
* @returns {Symbol}
*/
var arraySum = function (arr, toNumber) {
var sum = new Symbol(0);
for(var i = 0; i < arr.length; i++) {
var x = arr[i];
// Convert to symbol if not
sum = _.add(sum, !isSymbol(x) ? _.parse(x) : x);
}
return toNumber ? Number(sum) : sum;
};
/**
* Separates out the variables into terms of variabls.
* e.g. x+y+x*y+sqrt(2)+pi returns
* {x: x, y: y, x y: x*y, constants: sqrt(2)+pi
* @param {type} symbol
* @param {type} o
* @returns {undefined}
* @throws {Error} for expontentials
*/
var separate = function (symbol, o) {
symbol = _.expand(symbol);
o = o || {};
var insert = function (key, sym) {
if(!o[key])
o[key] = new Symbol(0);
o[key] = _.add(o[key], sym.clone());
};
symbol.each(function (x) {
if(x.isConstant('all')) {
insert('constants', x);
}
else if(x.group === S) {
insert(x.value, x);
}
else if(x.group === FN && (x.fname === ABS || x.fname === '')) {
separate(x.args[0]);
}
else if(x.group === EX || x.group === FN) {
throw new Error('Unable to separate. Term cannot be a function!');
}
else {
insert(variables(x).join(' '), x);
}
});
return o;
};
/**
* Fills holes in an array with zero symbol or generates one with n zeroes
* @param {Array} arr
* @param {Number} n
*/
var fillHoles = function (arr, n) {
n = n || arr.length;
for(var i = 0; i < n; i++) {
var sym = arr[i];
if(!sym)
arr[i] = new Symbol(0);
}
return arr;
};
/**
*
* Checks to see if the object provided is a Vector
* @param {Object} obj
*/
var isVector = function (obj) {
return (obj instanceof Vector);
};
/**
* Checks to see if the object provided is a Matrix
* @param {Object} obj
*/
var isMatrix = function (obj) {
return (obj instanceof Matrix);
};
var isSet = function (obj) {
return (obj instanceof Set);
};
/**
* Checks to see if a symbol is in group N
* @param {Symbol} symbol
*/
var isNumericSymbol = function (symbol) {
return symbol.group === N || symbol.group === P;
};
/**
* Checks to see if a symbol is a variable with no multiplier nor power
* @param {Symbol} symbol
*/
var isVariableSymbol = function (symbol) {
return symbol.group === S && symbol.multiplier.equals(1) && symbol.power.equals(1);
};
/**
* Checks to see if the object provided is an Array
* @param {Object} arr
*/
var isArray = function (arr) {
return Array.isArray(arr);
};
/**
* Checks to see if a number is an integer
* @param {Number} num
*/
var isInt = function (num) {
return /^[-+]?\d+e?\+?\d*$/gim.test(num.toString());
};
/**
* @param {Number|Symbol} obj
* @returns {boolean}
*/
var isNegative = function (obj) {
if(isSymbol(obj)) {
obj = obj.multiplier;
}
return obj.lessThan(0);
};
/**
* Safely stringify object
* @param o
*/
var stringify = function (o) {
if(!o)
return o;
return String(o);
};
/**
* @param {String} str
* @returns {String} - returns a formatted string surrounded by brackets
*/
var inBrackets = function (str) {
return '(' + str + ')';
};
/**
* A helper function to replace parts of string
* @param {String} str - The original string
* @param {Integer} from - The starting index
* @param {Integer} to - The ending index
* @param {String} with_str - The replacement string
* @returns {String} - A formatted string
*/
var stringReplace = function (str, from, to, with_str) {
return str.substr(0, from) + with_str + str.substr(to, str.length);
};
/**
* the Parser uses this to check if it's allowed to convert the obj to type Symbol
* @param {Object} obj
* @returns {boolean}
*/
var customType = function (obj) {
return obj !== undefined && obj.custom;
};
/**
* Checks to see if numbers are both negative or are both positive
* @param {Number} a
* @param {Number} b
* @returns {boolean}
*/
var sameSign = function (a, b) {
return (a < 0) === (b < 0);
};
/**
* A helper function to replace multiple occurences in a string. Takes multiple arguments
* @example format('{0} nice, {0} sweet', 'something')
* //returns 'something nice, something sweet'
*/
var format = function () {
var args = [].slice.call(arguments),
str = args.shift();
var new_str = str.replace(/{(\d+)}/g, function (match, index) {
var arg = args[index];
return typeof arg === 'function' ? arg() : arg;
});
return new_str;
};
/**
* Generates an array with values within a range. Multiplies by a step if provided
* @param {Number} start
* @param {Number} end
* @param {Number} step
*/
var range = function (start, end, step) {
var arr = [];
step = step || 1;
for(var i = start; i <= end; i++)
arr.push(i * step);
return arr;
};
/**
* Returns an array of all the keys in an array
* @param {Object} obj
* @returns {Array}
*/
var keys = Object.keys;
/**
* Returns the first encountered item in an object. Items do not have a fixed order in objects
* so only use if you need any first random or if there's only one item in the object
* @param {Object} obj
* @param {String} key Return this key as first object
* @param {Boolean} both
* @returns {*}
*/
var firstObject = function (obj, key, both) {
for(var x in obj)
break;
if(key)
return x;
if(both)
return {
key: x,
obj: obj[x]
};
return obj[x];
};
/**
* Substitutes out variables for two symbols, parses them to a number and them compares them numerically
* @param {Symbol} sym1
* @param {Symbol} sym2
* @param {String[]} vars - an optional array of variables to use
* @returns {bool}
*/
var compare = function (sym1, sym2, vars) {
var n = 5; //a random number between 1 and 5 is good enough
var scope = {}; // scope object with random numbers generated using vars
var comparison;
for(var i = 0; i < vars.length; i++)
scope[vars[i]] = new Symbol(Math.floor(Math.random() * n) + 1);
block('PARSE2NUMBER', function () {
comparison = _.parse(sym1, scope).equals(_.parse(sym2, scope));
});
return comparison;
};
/**
* Is used to set a user defined function using the function assign operator
* @param {String} name
* @param {String[]} params_array
* @param {String} body
* @returns {Boolean}
*/
var setFunction = function (name, params_array, body) {
validateName(name);
if(!isReserved(name)) {
params_array = params_array || variables(_.parse(body));
// The function gets set to PARSER.mapped function which is just
// a generic function call.
_.functions[name] = [_.mapped_function, params_array.length, {
name: name,
params: params_array,
body: body
}];
return body;
}
return null;
};
/**
* Returns the minimum number in an array
* @param {Array} arr
* @returns {Number}
*/
var arrayMax = function (arr) {
return Math.max.apply(undefined, arr);
};
/**
* Returns the maximum number in an array
* @param {Array} arr
* @returns {Number}
*/
var arrayMin = function (arr) {
return Math.min.apply(undefined, arr);
};
/**
* Checks to see if two arrays are equal
* @param {Array} arr1
* @param {Array} arr2
*/
var arrayEqual = function (arr1, arr2) {
arr1.sort();
arr2.sort();
// The must be of the same length
if(arr1.length === arr2.length) {
for(var i = 0; i < arr1.length; i++) {
// If any two items don't match we're done
if(arr1[i] !== arr2[i]) {
return false;
}
}
// Otherwise they're equal
return true;
}
return false;
};
/**
* Clones array with clonable items
* @param {Array} arr
* @returns {Array}
*/
var arrayClone = function (arr) {
var new_array = [], l = arr.length;
for(var i = 0; i < l; i++)
new_array[i] = arr[i].clone();
return new_array;
};
/**
* Fills numbers between array values
* @param {Numbers[]} arr
* @param {Integer} slices
*/
var arrayAddSlices = function (arr, slices) {
slices = slices || 20;
var retval = [];
var c, delta, e;
retval.push(arr[0]); //push the beginning
for(var i = 0; i < arr.length - 1; i++) {
c = arr[i];
delta = arr[i + 1] - c; //get the difference
e = delta / slices; //chop it up in the desired number of slices
for(var j = 0; j < slices; j++) {
c += e; //add the mesh to the last slice
retval.push(c);
}
}
return retval;
};
/**
* Gets nth roots of a number
* @param {Symbol} symbol
* @returns {Vector}
*/
var nroots = function (symbol) {
var a, b;
if(symbol.group === FN && symbol.fname === '') {
a = Symbol.unwrapPARENS(_.parse(symbol).toLinear());
b = _.parse(symbol.power);
}
else if(symbol.group === P) {
a = _.parse(symbol.value);
b = _.parse(symbol.power);
}
if(a && b && (a.group === N) && b.group === N && a.multiplier.isNegative()) {
var _roots = [];
var parts = Symbol.toPolarFormArray(evaluate(symbol));
var r = parts[0];
//var r = _.parse(a).abs().toString();
//https://en.wikipedia.org/wiki/De_Moivre%27s_formula
var x = _.arg(a);
var n = b.multiplier.den.toString();
var p = b.multiplier.num.toString();
var formula = '(({0})^({1})*(cos({3})+({2})*sin({3})))^({4})';
for(var i = 0; i < n; i++) {
var t = evaluate(_.parse(format("(({0})+2*pi*({1}))/({2})", x, i, n))).multiplier.toDecimal();
_roots.push(evaluate(_.parse(format(formula, r, n, Settings.IMAGINARY, t, p))));
}
return Vector.fromArray(_roots);
}
else if(symbol.isConstant(true, true)) {
var sign = symbol.sign();
var x = evaluate(symbol.abs());
var root = _.sqrt(x);
var _roots = [root.clone(), root.negate()];
if(sign < 0)
_roots = _roots.map(function (x) {
return _.multiply(x, Symbol.imaginary());
});
}
else {
_roots = [_.parse(symbol)];
}
return Vector.fromArray(_roots);
};
/**
* Sorts and array given 2 parameters
* @param {String} a
* @param {String} b
*/
var comboSort = function (a, b) {
var l = a.length,
combined = []; //the linker
for(var i = 0; i < a.length; i++) {
combined.push([a[i], b[i]]); //create the map
}
combined.sort(function (x, y) {
return x[0] - y[0];
});
var na = [], nb = [];
for(i = 0; i < l; i++) {
na.push(combined[i][0]);
nb.push(combined[i][1]);
}
return [na, nb];
};
/**
* TODO: Pick a more descriptive name and better description
* Breaks a function down into it's parts wrt to a variable, mainly coefficients
* Example a*x^2+b wrt x
* @param {Symbol} fn
* @param {String} wrt
* @param {bool} as_obj
*/
var decompose_fn = function (fn, wrt, as_obj) {
wrt = String(wrt); //convert to string
var ax, a, x, b;
if(fn.group === CP) {
var t = _.expand(fn.clone()).stripVar(wrt);
ax = _.subtract(fn.clone(), t.clone());
b = t;
}
else
ax = fn.clone();
a = ax.stripVar(wrt);
x = _.divide(ax.clone(), a.clone());
b = b || new Symbol(0);
if(as_obj)
return {
a: a,
x: x,
ax: ax,
b: b
};
return [a, x, ax, b];
};
/**
* Rounds a number up to x decimal places
* @param {Number} x
* @param {Number} s
*/
var nround = function (x, s) {
if(isInt(x)) {
if(x >= Number.MAX_VALUE)
return x.toString();
return Number(x);
}
s = typeof s === 'undefined' ? 14 : s;
return Math.round(x * Math.pow(10, s)) / Math.pow(10, s);
};
/**
* Is used for u-substitution. Gets a suitable u for substitution. If for
* instance a is used in the symbol then it keeps going down the line until
* one is found that's not in use. If all letters are taken then it
* starts appending numbers.
* IMPORTANT! It assumes that the substitution will be undone
* beore the user gets to interact with the object again.
* @param {Symbol} symbol
*/
var getU = function (symbol) {
//start with u
var u = 'u', //start with u
v = u, //init with u
c = 0, //postfix number
vars = variables(symbol);
//make sure this variable isn't reserved and isn't in the variable list
while(!(RESERVED.indexOf(v) === - 1 && vars.indexOf(v) === - 1))
v = u + c++;
//get an empty slot. It seems easier to just push but the
//problem is that we may have some which are created by clearU
for(var i = 0, l = RESERVED.length; i <= l; i++)
//reserved cannot equals false or 0 so we can safely check for a falsy type
if(!RESERVED[i]) {
RESERVED[i] = v; //reserve the variable
break;
}
return v;
};
/**
* Clears the u variable so it's no longer reserved
* @param {String} u
*/
var clearU = function (u) {
var indx = RESERVED.indexOf(u);
if(indx !== -1)
RESERVED[indx] = undefined;
};
/**
* Loops through each item in object and calls function with item as param
* @param {Object|Array} obj
* @param {Function} fn
*/
var each = function (obj, fn) {
if(isArray(obj)) {
var l = obj.length;
for(var i = 0; i < l; i++)
fn.call(obj, i);
}
else {
for(var x in obj)
if(obj.hasOwnProperty(x))
fn.call(obj, x);
}
};
/**
* Checks to see if a number is an even number
* @param {Number} num
* @returns {boolean}
*/
var even = function (num) {
return num % 2 === 0;
};
/**
* Checks to see if a fraction is divisible by 2
* @param {Number} num
* @returns {boolean}
*/
var evenFraction = function (num) {
return 1 / (num % 1) % 2 === 0;
};
/**
* Strips duplicates out of an array
* @param {Array} arr
*/
var arrayUnique = function (arr) {
var l = arr.length, a = [];
for(var i = 0; i < l; i++) {
var item = arr[i];
if(a.indexOf(item) === -1)
a.push(item);
}
return a;
};
/**
* Gets all the variables in an array of Symbols
* @param {Symbol[]} arr
*/
var arrayGetVariables = function (arr) {
var vars = variables(arr[0], null, null, true);
//get all variables
for(var i = 1, l = arr.length; i < l; i++)
vars = vars.concat(variables(arr[i]));
//remove duplicates
vars = arrayUnique(vars).sort();
//done
return vars;
};
/**
* Removes duplicates from an array. Returns a new array
* @param {Array} arr
* @param {Function} condition
*/
var removeDuplicates = function (arr, condition) {
var conditionType = typeof condition;
if(conditionType !== 'function' || conditionType === 'undefined') {
condition = function (a, b) {
return a === b;
};
}
var seen = [];
while(arr.length) {
var a = arr[0];
//only one element left so we're done
if(arr.length === 1) {
seen.push(a);
break;
}
var temp = [];
seen.push(a); //we already scanned these
for(var i = 1; i < arr.length; i++) {
var b = arr[i];
//if the number is outside the specified tolerance
if(!condition(a, b))
temp.push(b);
}
//start over with the remainder
arr = temp;
}
return seen;
};
/**
* Reserves the names in an object so they cannot be used as function names
* @param {Object} obj
*/
var reserveNames = function (obj) {
var add = function (item) {
if(RESERVED.indexOf(item) === -1)
RESERVED.push(item);
};
if(typeof obj === 'string')
add(obj);
else {
each(obj, function (x) {
add(x);
});
}
};
/**
* Removes an item from either an array or an object. If the object is an array, the index must be
* specified after the array. If it's an object then the key must be specified
* @param {Object|Array} obj
* @param {Integer} indexOrKey
*/
var remove = function (obj, indexOrKey) {
var result;
if(isArray(obj)) {
result = obj.splice(indexOrKey, 1)[0];
}
else {
result = obj[indexOrKey];
delete obj[indexOrKey];
}
return result;
};
/**
* Creates a temporary block in which one of the global settings is temporarily modified while
* the function is called. For instance if you want to parse directly to a number rather than have a symbolic
* answer for a period you would set PARSE2NUMBER to true in the block.
* @example block('PARSE2NUMBER', function(){//symbol being parsed to number}, true);
* @param {String} setting - The setting being accessed
* @param {Function} f
* @param {boolean} opt - The value of the setting in the block
* @param {String} obj - The obj of interest. Usually a Symbol but could be any object
*/
var block = function (setting, f, opt, obj) {
var current_setting = Settings[setting];
Settings[setting] = opt === undefined ? true : !!opt;
var retval = f.call(obj);
Settings[setting] = current_setting;
return retval;
};
/**
* provide a mechanism for accessing functions directly. Not yet complete!!!
* Some functions will return undefined. This can maybe just remove the
* function object at some point when all functions are eventually
* housed in the global function object. Returns ALL parser available
* functions. Parser.functions may not contain all functions
*/
var importFunctions = function () {
var o = {};
for(var x in _.functions)
o[x] = _.functions[x][0];
return o;
};
/**
* Converts function arguments to an array. Now used by gcd and lcm in Algebra.js :)
* @param {Array|object} obj
*/
var arguments2Array = function (obj) {
return [].slice.call(obj);
};
/**
* Returns the coefficients of a symbol given a variable. Given ax^2+b^x+c, it divides
* each nth term by x^n.
* @param {Symbol} symbol
* @param {Symbol} wrt
*/
var getCoeffs = function (symbol, wrt, info) {
var coeffs = [];
//we loop through the symbols and stick them in their respective
//containers e.g. y*x^2 goes to index 2
symbol.each(function (term) {
if(term.contains(wrt)) {
//we want only the coefficient which in this case will be everything but the variable
//e.g. a*b*x -> a*b if the variable to solve for is x
var coeff = term.stripVar(wrt),
x = _.divide(term.clone(), coeff.clone()),
p = x.power.toDecimal();
}
else {
coeff = term;
p = 0;
}
var e = coeffs[p];
//if it exists just add it to it
coeffs[p] = e ? _.add(e, coeff) : coeff;
}, true);
for(var i = 0; i < coeffs.length; i++)
if(!coeffs[i])
coeffs[i] = new Symbol(0);
//fill the holes
return coeffs;
};
/**
* As the name states. It forces evaluation of the expression
* @param {Symbol} symbol
* @param {Symbol} o
*/
var evaluate = function (symbol, o) {
return block('PARSE2NUMBER', function () {
return _.parse(symbol, o);
}, true);
};
/**
* Converts an array to a vector. Consider moving this to Vector.fromArray
* @param {String[]|String|Symbol|Number|Number[]} x
*/
var convertToVector = function (x) {
if(isArray(x)) {
var vector = new Vector([]);
for(var i = 0; i < x.length; i++)
vector.elements.push(convertToVector(x[i]));
return vector;
}
//Ensure that a nerdamer ready object is returned
if(!isSymbol(x))
return _.parse(x);
return x;
};
/**
* Generates prime numbers up to a specified number
* @param {Number} upto
*/
var generatePrimes = function (upto) {
//get the last prime in the array
var last_prime = PRIMES[PRIMES.length - 1] || 2;
//no need to check if we've already encountered the number. Just check the cache.
for(var i = last_prime; i < upto; i++) {
if(isPrime(i))
PRIMES.push(i);
}
};
/**
* Checks to see if all arguments are numbers
* @param {object} args
*/
var allNumbers = function (args) {
for(var i = 0; i < args.length; i++)
if(args[i].group !== N)
return false;
return true;
};
/*
* Checks if all arguments aren't just all number but if they
* are constants as well e.g. pi, e.
* @param {object} args
*/
var allConstants = function (args) {
for(var i = 0; i < args.length; i++) {
if(args[i].isPi() || args[i].isE())
continue;
if(!args[i].isConstant(true))
return false;
}
return true;
};
/**
* Used to multiply two expression in expanded form
* @param {Symbol} a
* @param {Symbol} b
*/
var mix = function (a, b, opt) {
// Flip them if b is a CP or PL and a is not
if(b.isComposite() && !a.isComposite() || b.isLinear() && !a.isLinear()) {
[a, b] = [b, a];
}
// A temporary variable to hold the expanded terms
var t = new Symbol(0);
if(a.isLinear()) {
a.each(function (x) {
// If b is not a PL or a CP then simply multiply it
if(!b.isComposite()) {
var term = _.multiply(_.parse(x), _.parse(b));
t = _.add(t, _.expand(term, opt));
}
// Otherwise multiply out each term.
else if(b.isLinear()) {
b.each(function (y) {
var term = _.multiply(_.parse(x), _.parse(y));
var expanded = _.expand(_.parse(term), opt);
t = _.add(t, expanded);
}, true);
}
else {
t = _.add(t, _.multiply(x, _.parse(b)));
}
}, true);
}
else {
// Just multiply them together
t = _.multiply(a, b);
}
// The expanded function is now t
return t;
};
//Exceptions ===================================================================
//Is thrown for division by zero
var DivisionByZero = customError('DivisionByZero');
// Is throw if an error occured during parsing
var ParseError = customError('ParseError');
// Is thrown if the expression results in undefined
var UndefinedError = customError('UndefinedError');
// Is throw input is out of the function domain
var OutOfFunctionDomainError = customError('OutOfFunctionDomainError');
// Is throw if a function exceeds x amount of iterations
var MaximumIterationsReached = customError('MaximumIterationsReached');
// Is thrown if the parser receives an incorrect type
var NerdamerTypeError = customError('NerdamerTypeError');
// Is thrown if bracket parity is not correct
var ParityError = customError('ParityError');
// Is thrown if an unexpectd or incorrect operator is encountered
var OperatorError = customError('OperatorError');
// Is thrown if an index is out of range.
var OutOfRangeError = customError('OutOfRangeError');
// Is thrown if dimensions are incorrect. Mostly for matrices
var DimensionError = customError('DimensionError');
// Is thrown if variable name violates naming rule
var InvalidVariableNameError = customError('InvalidVariableNameError');
// Is thrown if the limits of the library are exceeded for a function
// This can be that the function become unstable passed a value
var ValueLimitExceededError = customError('ValueLimitExceededError');
// Is throw if the value is an incorrect LH or RH value
var NerdamerValueError = customError('NerdamerValueError');
// Is thrown if the value is an incorrect LH or RH value
var SolveError = customError('SolveError');
// Is thrown for an infinite loop
var InfiniteLoopError = customError('InfiniteLoopError');
// Is thrown if an operator is found when there shouldn't be one
var UnexpectedTokenError = customError('UnexpectedTokenError');
var exceptions = {
DivisionByZero: DivisionByZero,
ParseError: ParseError,
OutOfFunctionDomainError: OutOfFunctionDomainError,
UndefinedError: UndefinedError,
MaximumIterationsReached: MaximumIterationsReached,
NerdamerTypeError: NerdamerTypeError,
ParityError: ParityError,
OperatorError: OperatorError,
OutOfRangeError: OutOfRangeError,
DimensionError: DimensionError,
InvalidVariableNameError: InvalidVariableNameError,
ValueLimitExceededError: ValueLimitExceededError,
NerdamerValueError: NerdamerValueError,
SolveError: SolveError,
InfiniteLoopError: InfiniteLoopError,
UnexpectedTokenError: UnexpectedTokenError
};
//Math2 ========================================================================
//This object holds additional functions for nerdamer. Think of it as an extension of the Math object.
//I really don't like touching objects which aren't mine hence the reason for Math2. The names of the
//functions within are pretty self-explanatory.
//NOTE: DO NOT USE INLINE COMMENTS WITH THE MATH2 OBJECT! THIS BREAK DURING COMPILATION OF BUILDFUNCTION.
var Math2 = {
csc: function (x) {
return 1 / Math.sin(x);
},
sec: function (x) {
return 1 / Math.cos(x);
},
cot: function (x) {
return 1 / Math.tan(x);
},
acsc: function (x) {
return Math.asin(1 / x);
},
asec: function (x) {
return Math.acos(1 / x);
},
acot: function (x) {
return (Math.PI / 2) - Math.atan(x);
},
// https://gist.github.com/jiggzson/df0e9ae8b3b06ff3d8dc2aa062853bd8
erf: function (x) {
var t = 1 / (1 + 0.5 * Math.abs(x));
var result = 1 - t * Math.exp(-x * x - 1.26551223 +
t * (1.00002368 +
t * (0.37409196 +
t * (0.09678418 +
t * (-0.18628806 +
t * (0.27886807 +
t * (-1.13520398 +
t * (1.48851587 +
t * (-0.82215223 +
t * (0.17087277)))))))))
);
return x >= 0 ? result : -result;
},
diff: function (f) {
var h = 0.001;
var derivative = function (x) {
return (f(x + h) - f(x - h)) / (2 * h);
};
return derivative;
},
median: function (...values) {
values.sort(function (a, b) {
return a - b;
});
var half = Math.floor(values.length / 2);
if(values.length % 2)
return values[half];
return (values[half - 1] + values[half]) / 2.0;
},
/*
* Reverses continued fraction calculation
* @param {obj} contd
* @returns {Number}
*/
fromContinued: function (contd) {
var arr = contd.fractions.slice();
var e = 1 / arr.pop();
for(var i = 0, l = arr.length; i < l; i++) {
e = 1 / (arr.pop() + e);
}
return contd.sign * (contd.whole + e);
},
/*
* Calculates continued fractions
* @param {Number} n
* @param {Number} x The number of places
* @returns {Number}
*/
continuedFraction: function (n, x) {
x = x || 20;
var sign = Math.sign(n); /*store the sign*/
var absn = Math.abs(n); /*get the absolute value of the number*/
var whole = Math.floor(absn); /*get the whole*/
var ni = absn - whole; /*subtract the whole*/
var c = 0; /*the counter to keep track of iterations*/
var done = false;
var epsilon = 1e-14;
var max = 1e7;
var e, w;
var retval = {
whole: whole,
sign: sign,
fractions: []
};
/*start calculating*/
while(!done && ni !== 0) {
/*invert and get the whole*/
e = 1 / ni;
w = Math.floor(e);
if(w > max) {
/*this signals that we may have already gone too far*/
var d = Math2.fromContinued(retval) - n;
if(d <= Number.EPSILON)
break;
}
/*add to result*/
retval.fractions.push(w);
/*move the ni to the decimal*/
ni = e - w;
/*ni should always be a decimal. If we have a whole number then we're in the rounding errors*/
if(ni <= epsilon || c >= x - 1)
done = true;
c++;
}
/*cleanup 1/(n+1/1) = 1/(n+1) so just move the last digit one over if it's one*/
var idx = retval.fractions.length - 1;
if(retval.fractions[idx] === 1) {
retval.fractions.pop();