'use strict'; var digits = require('./../../../utils/number').digits; // TODO this could be improved by simplifying seperated constants under associative and commutative operators function factory(type, config, load, typed, math) { var util = load(require('./util')); var isCommutative = util.isCommutative; var isAssociative = util.isAssociative; var allChildren = util.allChildren; var createMakeNodeFunction = util.createMakeNodeFunction; var ConstantNode = math.expression.node.ConstantNode; var OperatorNode = math.expression.node.OperatorNode; function simplifyConstant(expr) { var res = foldFraction(expr); return res.isNode ? res : _toNode(res); } function _eval(fnname, args) { try { return _toNumber(math[fnname].apply(null, args)); } 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 (x.isFraction) { return x.valueOf(); } return x; }); return _toNumber(math[fnname].apply(null, args)); } } var _toNode = typed({ 'Fraction': _fractionToNode, 'number': function(n) { if (n < 0) { return unaryMinusNode(new ConstantNode(-n)); } return new ConstantNode(n); }, 'BigNumber': function(n) { if (n < 0) { return unaryMinusNode(new ConstantNode(n.negated().toString(), 'number')); } return new ConstantNode(n.toString(), 'number'); }, 'Complex': function(s) { throw 'Cannot convert Complex number to Node'; } }); // convert a number to a fraction only if it can be expressed exactly function _exactFraction(n) { if (isFinite(n)) { var f = math.fraction(n); if (f.valueOf() === n) { 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': function(s) { if (config.number === 'BigNumber') { return math.bignumber(s); } else if (config.number === 'Fraction') { return math.fraction(s); } else { return _exactFraction(parseFloat(s)); } }, 'Fraction': function(s) { return s; }, 'BigNumber': function(s) { return s; }, 'number': function(s) { return _exactFraction(s); }, 'Complex': function(s) { if (s.im !== 0) { return s; } return _exactFraction(s.re); }, }); 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) { return args.reduce(function(a, b) { if (!a.isNode && !b.isNode) { try { return _eval(fn, [a,b]); } catch (ignoreandcontinue) {} a = _toNode(a); b = _toNode(b); } else if (!a.isNode) { a = _toNode(a); } else if (!b.isNode) { b = _toNode(b); } return makeNode([a, b]); }); } // destroys the original node and returns a folded one function foldFraction(node) { switch(node.type) { case 'SymbolNode': return node; case 'ConstantNode': if (node.valueType === 'number') { return _toNumber(node.value); } return node; case 'FunctionNode': if (math[node.name] && math[node.name].rawArgs) { return node; } /* falls through */ case 'OperatorNode': var fn = node.fn.toString(); var args; var res; var makeNode = createMakeNodeFunction(node); if (node.args.length === 1) { args = [foldFraction(node.args[0])]; if (!args[0].isNode) { res = _eval(fn, args); } else { res = makeNode(args); } } else if (isAssociative(node)) { args = allChildren(node); args = args.map(foldFraction); if (isCommutative(fn)) { // commutative binary operator var consts = [], vars = []; for (var i=0; i < args.length; i++) { if (!args[i].isNode) { consts.push(args[i]); } else { vars.push(args[i]); } } if (consts.length > 1) { res = foldOp(fn, consts, makeNode); vars.unshift(res); res = foldOp(fn, vars, makeNode); } else { // we won't change the children order since it's not neccessary res = foldOp(fn, args, makeNode); } } else { // non-commutative binary operator res = foldOp(fn, args, makeNode); } } else { // non-associative binary operator args = node.args.map(foldFraction); res = foldOp(fn, args, makeNode); } return res; case 'ParenthesisNode': // remove the uneccessary parenthesis return foldFraction(node.content); 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 'UpdateNode': /* falls through */ case 'ConditionalNode': /* falls through */ default: throw 'Unimplemented node type in simplifyConstant: '+node.type; } } return simplifyConstant; } exports.math = true; exports.name = 'simplifyConstant'; exports.path = 'algebra.simplify'; exports.factory = factory;