UNPKG

mathjs

Version:

Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif

316 lines (259 loc) 9.06 kB
// TODO this could be improved by simplifying seperated constants under associative and commutative operators import { isFraction, isNode, isOperatorNode } from '../../../utils/is.js'; import { factory } from '../../../utils/factory.js'; import { createUtil } from './util.js'; import { noBignumber, noFraction } from '../../../utils/noop.js'; var name = 'simplifyConstant'; var dependencies = ['typed', 'config', 'mathWithTransform', '?fraction', '?bignumber', 'ConstantNode', 'OperatorNode', 'FunctionNode', 'SymbolNode']; export var createSimplifyConstant = /* #__PURE__ */factory(name, dependencies, _ref => { var { typed, config, mathWithTransform, fraction, bignumber, ConstantNode, OperatorNode, FunctionNode, SymbolNode } = _ref; var { isCommutative, isAssociative, allChildren, createMakeNodeFunction } = createUtil({ FunctionNode, OperatorNode, SymbolNode }); function simplifyConstant(expr, options) { var res = foldFraction(expr, options); return isNode(res) ? res : _toNode(res); } function _eval(fnname, args, options) { try { return _toNumber(mathWithTransform[fnname].apply(null, args), options); } catch (ignore) { // sometimes the implicit type conversion causes the evaluation to fail, so we'll try again after removing Fractions args = args.map(function (x) { if (isFraction(x)) { return x.valueOf(); } return x; }); return _toNumber(mathWithTransform[fnname].apply(null, args), options); } } var _toNode = typed({ Fraction: _fractionToNode, number: function number(n) { if (n < 0) { return unaryMinusNode(new ConstantNode(-n)); } return new ConstantNode(n); }, BigNumber: function BigNumber(n) { if (n < 0) { return unaryMinusNode(new ConstantNode(-n)); } return new ConstantNode(n); // old parameters: (n.toString(), 'number') }, Complex: function Complex(s) { throw new Error('Cannot convert Complex number to Node'); } }); // convert a number to a fraction only if it can be expressed exactly, // and when both numerator and denominator are small enough function _exactFraction(n, options) { var exactFractions = options && options.exactFractions !== false; if (exactFractions && isFinite(n) && fraction) { var f = fraction(n); var fractionsLimit = options && typeof options.fractionsLimit === 'number' ? options.fractionsLimit : Infinity; // no limit by default if (f.valueOf() === n && f.n < fractionsLimit && f.d < fractionsLimit) { return f; } } return n; } // Convert numbers to a preferred number type in preference order: Fraction, number, Complex // BigNumbers are left alone var _toNumber = typed({ 'string, Object': function stringObject(s, options) { if (config.number === 'BigNumber') { if (bignumber === undefined) { noBignumber(); } return bignumber(s); } else if (config.number === 'Fraction') { if (fraction === undefined) { noFraction(); } return fraction(s); } else { var n = parseFloat(s); return _exactFraction(n, options); } }, 'Fraction, Object': function FractionObject(s, options) { return s; }, // we don't need options here 'BigNumber, Object': function BigNumberObject(s, options) { return s; }, // we don't need options here 'number, Object': function numberObject(s, options) { return _exactFraction(s, options); }, 'Complex, Object': function ComplexObject(s, options) { if (s.im !== 0) { return s; } return _exactFraction(s.re, options); } }); function unaryMinusNode(n) { return new OperatorNode('-', 'unaryMinus', [n]); } function _fractionToNode(f) { var n; var vn = f.s * f.n; if (vn < 0) { n = new OperatorNode('-', 'unaryMinus', [new ConstantNode(-vn)]); } else { n = new ConstantNode(vn); } if (f.d === 1) { return n; } return new OperatorNode('/', 'divide', [n, new ConstantNode(f.d)]); } /* * Create a binary tree from a list of Fractions and Nodes. * Tries to fold Fractions by evaluating them until the first Node in the list is hit, so * `args` should be sorted to have the Fractions at the start (if the operator is commutative). * @param args - list of Fractions and Nodes * @param fn - evaluator for the binary operation evaluator that accepts two Fractions * @param makeNode - creates a binary OperatorNode/FunctionNode from a list of child Nodes * if args.length is 1, returns args[0] * @return - Either a Node representing a binary expression or Fraction */ function foldOp(fn, args, makeNode, options) { return args.reduce(function (a, b) { if (!isNode(a) && !isNode(b)) { try { return _eval(fn, [a, b], options); } catch (ignoreandcontinue) {} a = _toNode(a); b = _toNode(b); } else if (!isNode(a)) { a = _toNode(a); } else if (!isNode(b)) { b = _toNode(b); } return makeNode([a, b]); }); } // destroys the original node and returns a folded one function foldFraction(node, options) { switch (node.type) { case 'SymbolNode': return node; case 'ConstantNode': if (typeof node.value === 'number' || !isNaN(node.value)) { return _toNumber(node.value, options); } return node; case 'FunctionNode': if (mathWithTransform[node.name] && mathWithTransform[node.name].rawArgs) { return node; } { // Process operators as OperatorNode var operatorFunctions = ['add', 'multiply']; if (operatorFunctions.indexOf(node.name) === -1) { var args = node.args.map(arg => foldFraction(arg, options)); // If all args are numbers if (!args.some(isNode)) { try { return _eval(node.name, args, options); } catch (ignoreandcontine) {} } // Convert all args to nodes and construct a symbolic function call args = args.map(function (arg) { return isNode(arg) ? arg : _toNode(arg); }); return new FunctionNode(node.name, args); } else {// treat as operator } } /* falls through */ case 'OperatorNode': { var fn = node.fn.toString(); var _args; var res; var makeNode = createMakeNodeFunction(node); if (isOperatorNode(node) && node.isUnary()) { _args = [foldFraction(node.args[0], options)]; if (!isNode(_args[0])) { res = _eval(fn, _args, options); } else { res = makeNode(_args); } } else if (isAssociative(node)) { _args = allChildren(node); _args = _args.map(arg => foldFraction(arg, options)); if (isCommutative(fn)) { // commutative binary operator var consts = []; var vars = []; for (var i = 0; i < _args.length; i++) { if (!isNode(_args[i])) { consts.push(_args[i]); } else { vars.push(_args[i]); } } if (consts.length > 1) { res = foldOp(fn, consts, makeNode, options); vars.unshift(res); res = foldOp(fn, vars, makeNode, options); } else { // we won't change the children order since it's not neccessary res = foldOp(fn, _args, makeNode, options); } } else { // non-commutative binary operator res = foldOp(fn, _args, makeNode, options); } } else { // non-associative binary operator _args = node.args.map(arg => foldFraction(arg, options)); res = foldOp(fn, _args, makeNode, options); } return res; } case 'ParenthesisNode': // remove the uneccessary parenthesis return foldFraction(node.content, options); case 'AccessorNode': /* falls through */ case 'ArrayNode': /* falls through */ case 'AssignmentNode': /* falls through */ case 'BlockNode': /* falls through */ case 'FunctionAssignmentNode': /* falls through */ case 'IndexNode': /* falls through */ case 'ObjectNode': /* falls through */ case 'RangeNode': /* falls through */ case 'ConditionalNode': /* falls through */ default: throw new Error("Unimplemented node type in simplifyConstant: ".concat(node.type)); } } return simplifyConstant; });