Get around floating point precision of JavaScript fixes #5
Fix `equation` not working on parentheses wrapped variables Fixed `parseExpression` not working correctly on nested function operators
This commit is contained in:
		
							
								
								
									
										36
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							@@ -72,7 +72,11 @@ var Equation = {
 | 
				
			|||||||
    var stack = parseExpression(expression);
 | 
					    var stack = parseExpression(expression);
 | 
				
			||||||
    var variables = [];
 | 
					    var variables = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stack.forEach(function (a) {
 | 
					    stack.forEach(function varCheck(a) {
 | 
				
			||||||
 | 
					      if (Array.isArray(a)) {
 | 
				
			||||||
 | 
					        return a.forEach(varCheck);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (typeof a === 'string' && !_.isNumber(a) && !_operators2['default'][a] && a === a.toLowerCase()) {
 | 
					      if (typeof a === 'string' && !_.isNumber(a) && !_operators2['default'][a] && a === a.toLowerCase()) {
 | 
				
			||||||
        // grouped variables like (y) need to have their parantheses removed
 | 
					        // grouped variables like (y) need to have their parantheses removed
 | 
				
			||||||
        variables.push(_.removeSymbols(a));
 | 
					        variables.push(_.removeSymbols(a));
 | 
				
			||||||
@@ -161,22 +165,30 @@ var parseExpression = function parseExpression(expression) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // it's probably a function with a length more than one
 | 
					    // it's probably a function with a length more than one
 | 
				
			||||||
    if (!_.isNumber(cur) && !_operators2['default'][cur] && cur !== '.') {
 | 
					    if (!_.isNumber(cur) && !_operators2['default'][cur] && cur !== '.' && cur !== '(' && cur !== ')') {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      record += cur;
 | 
					      record += cur;
 | 
				
			||||||
    } else if (record.length) {
 | 
					    } else if (record.length) {
 | 
				
			||||||
      stack.push(record, cur);
 | 
					      stack.push(record, cur);
 | 
				
			||||||
      record = '';
 | 
					      record = '';
 | 
				
			||||||
    } else if (_.isNumber(stack[stack.length - 1]) && (_.isNumber(cur) || cur === '.')) {
 | 
					    } else if (_.isNumber(stack[past]) && (_.isNumber(cur) || cur === '.')) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      stack[stack.length - 1] += cur;
 | 
					      stack[past] += cur;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // negation sign
 | 
				
			||||||
    } else if (stack[past] === '-') {
 | 
					    } else if (stack[past] === '-') {
 | 
				
			||||||
      var beforeSign = stack[stack.length - 2];
 | 
					      var beforeSign = stack[past - 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 2 / -5 is OK, pass
 | 
				
			||||||
      if (_operators2['default'][beforeSign]) {
 | 
					      if (_operators2['default'][beforeSign]) {
 | 
				
			||||||
        stack[past] += cur;
 | 
					        stack[past] += cur;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // (2+1) - 5 becomes (2+1) + -5
 | 
				
			||||||
      } else if (beforeSign === ')') {
 | 
					      } else if (beforeSign === ')') {
 | 
				
			||||||
        stack[past] = '+';
 | 
					        stack[past] = '+';
 | 
				
			||||||
        stack.push('-' + cur);
 | 
					        stack.push('-' + cur);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 2 - 5 is also OK, pass
 | 
				
			||||||
      } else if (_.isNumber(beforeSign)) {
 | 
					      } else if (_.isNumber(beforeSign)) {
 | 
				
			||||||
        stack.push(cur);
 | 
					        stack.push(cur);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@@ -353,7 +365,7 @@ var evaluate = function evaluate(stack) {
 | 
				
			|||||||
  var leftArguments = stack.slice(0, left),
 | 
					  var leftArguments = stack.slice(0, left),
 | 
				
			||||||
      rightArguments = stack.slice(left + 1);
 | 
					      rightArguments = stack.slice(left + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (_operators$op = _operators2['default'][op]).fn.apply(_operators$op, _toConsumableArray(leftArguments).concat(_toConsumableArray(rightArguments)));
 | 
					  return fixFloat((_operators$op = _operators2['default'][op]).fn.apply(_operators$op, _toConsumableArray(leftArguments).concat(_toConsumableArray(rightArguments))));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -413,5 +425,17 @@ var replaceConstants = function replaceConstants(expression) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					  * Fixes JavaScript's floating point precisions - Issue #5
 | 
				
			||||||
 | 
					  *
 | 
				
			||||||
 | 
					  * @param {Number} number
 | 
				
			||||||
 | 
					  *        The number to fix
 | 
				
			||||||
 | 
					  * @return {Number}
 | 
				
			||||||
 | 
					  *         Fixed number
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					var fixFloat = function fixFloat(number) {
 | 
				
			||||||
 | 
					  return +number.toFixed(15);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports['default'] = Equation;
 | 
					exports['default'] = Equation;
 | 
				
			||||||
module.exports = exports['default'];
 | 
					module.exports = exports['default'];
 | 
				
			||||||
							
								
								
									
										10
									
								
								dist/operators.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								dist/operators.js
									
									
									
									
										vendored
									
									
								
							@@ -109,6 +109,16 @@ exports['default'] = {
 | 
				
			|||||||
    fn: Math.cot,
 | 
					    fn: Math.cot,
 | 
				
			||||||
    format: '10',
 | 
					    format: '10',
 | 
				
			||||||
    precedence: -1
 | 
					    precedence: -1
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  round: {
 | 
				
			||||||
 | 
					    fn: Math.round,
 | 
				
			||||||
 | 
					    format: '10',
 | 
				
			||||||
 | 
					    precedence: -1
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  floor: {
 | 
				
			||||||
 | 
					    fn: Math.floor,
 | 
				
			||||||
 | 
					    format: '10',
 | 
				
			||||||
 | 
					    precedence: -1
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
module.exports = exports['default'];
 | 
					module.exports = exports['default'];
 | 
				
			||||||
							
								
								
									
										11
									
								
								dist/tests/solve.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								dist/tests/solve.js
									
									
									
									
										vendored
									
									
								
							@@ -32,6 +32,13 @@ describe('Basic math operators', function () {
 | 
				
			|||||||
  it('should work for multi-digit numbers', function () {
 | 
					  it('should work for multi-digit numbers', function () {
 | 
				
			||||||
    _expect.expect(_M2['default'].solve('12+15')).to.equal(27);
 | 
					    _expect.expect(_M2['default'].solve('12+15')).to.equal(27);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should deal with floating precision of javascript - #5', function () {
 | 
				
			||||||
 | 
					    _expect.expect(_M2['default'].solve('0.2 + 0.1')).to.equal(0.3);
 | 
				
			||||||
 | 
					    _expect.expect(_M2['default'].solve('0.2 + 0.4')).to.equal(0.6);
 | 
				
			||||||
 | 
					    _expect.expect(_M2['default'].solve('round(floor(1.23456789/0.2)) * 0.2')).to.equal(1.2);
 | 
				
			||||||
 | 
					    _expect.expect(_M2['default'].solve('1.23456789 - (1.23456789 % 0.2)')).to.equal(1.2);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Negative Numbers', function () {
 | 
					describe('Negative Numbers', function () {
 | 
				
			||||||
@@ -67,11 +74,11 @@ describe('Precedence', function () {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Functions', function () {
 | 
					describe('Functions', function () {
 | 
				
			||||||
  it('should work for with parantheses', function () {
 | 
					  it('should work with parantheses', function () {
 | 
				
			||||||
    _expect.expect(_M2['default'].solve('lg(4) * 5')).to.equal(10);
 | 
					    _expect.expect(_M2['default'].solve('lg(4) * 5')).to.equal(10);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work for without parantheses', function () {
 | 
					  it('should work without parantheses', function () {
 | 
				
			||||||
    _expect.expect(_M2['default'].solve('lg4 * 5')).to.equal(10);
 | 
					    _expect.expect(_M2['default'].solve('lg4 * 5')).to.equal(10);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										40
									
								
								lib/index.js
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								lib/index.js
									
									
									
									
									
								
							@@ -49,7 +49,11 @@ let Equation = {
 | 
				
			|||||||
    let stack = parseExpression(expression);
 | 
					    let stack = parseExpression(expression);
 | 
				
			||||||
    let variables = [];
 | 
					    let variables = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stack.forEach(a => {
 | 
					    stack.forEach(function varCheck(a) {
 | 
				
			||||||
 | 
					      if (Array.isArray(a)) {
 | 
				
			||||||
 | 
					        return a.forEach(varCheck);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (typeof a === 'string' && !_.isNumber(a) &&
 | 
					      if (typeof a === 'string' && !_.isNumber(a) &&
 | 
				
			||||||
          !operators[a] && a === a.toLowerCase()) {
 | 
					          !operators[a] && a === a.toLowerCase()) {
 | 
				
			||||||
        // grouped variables like (y) need to have their parantheses removed
 | 
					        // grouped variables like (y) need to have their parantheses removed
 | 
				
			||||||
@@ -125,23 +129,32 @@ const parseExpression = expression => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // it's probably a function with a length more than one
 | 
					    // it's probably a function with a length more than one
 | 
				
			||||||
    if (!_.isNumber(cur) && !operators[cur] && cur !== '.') {
 | 
					    if (!_.isNumber(cur) && !operators[cur] && cur !== '.' &&
 | 
				
			||||||
 | 
					        cur !== '(' && cur !== ')') {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      record += cur;
 | 
					      record += cur;
 | 
				
			||||||
    } else if (record.length) {
 | 
					    } else if (record.length) {
 | 
				
			||||||
      stack.push(record, cur);
 | 
					      stack.push(record, cur);
 | 
				
			||||||
      record = '';
 | 
					      record = '';
 | 
				
			||||||
    } else if (_.isNumber(stack[stack.length - 1]) &&
 | 
					    } else if (_.isNumber(stack[past]) &&
 | 
				
			||||||
              (_.isNumber(cur) || cur === '.')) {
 | 
					              (_.isNumber(cur) || cur === '.')) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      stack[stack.length - 1] += cur;
 | 
					      stack[past] += cur;
 | 
				
			||||||
    } else if (stack[past] === '-') {
 | 
					 | 
				
			||||||
      const beforeSign = stack[stack.length - 2];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // negation sign
 | 
				
			||||||
 | 
					    } else if (stack[past] === '-') {
 | 
				
			||||||
 | 
					      const beforeSign = stack[past - 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // 2 / -5 is OK, pass
 | 
				
			||||||
      if (operators[beforeSign]) {
 | 
					      if (operators[beforeSign]) {
 | 
				
			||||||
        stack[past] += cur;
 | 
					        stack[past] += cur;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // (2+1) - 5 becomes (2+1) + -5
 | 
				
			||||||
      } else if (beforeSign === ')') {
 | 
					      } else if (beforeSign === ')') {
 | 
				
			||||||
        stack[past] = '+';
 | 
					        stack[past] = '+';
 | 
				
			||||||
        stack.push(`-${cur}`);
 | 
					        stack.push(`-${cur}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 2 - 5 is also OK, pass
 | 
				
			||||||
      } else if (_.isNumber(beforeSign)) {
 | 
					      } else if (_.isNumber(beforeSign)) {
 | 
				
			||||||
        stack.push(cur);
 | 
					        stack.push(cur);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@@ -272,12 +285,13 @@ const evaluate = stack => {
 | 
				
			|||||||
  if (!op) {
 | 
					  if (!op) {
 | 
				
			||||||
    return stack[0];
 | 
					    return stack[0];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { left } = formatInfo(op);
 | 
					  const { left } = formatInfo(op);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let leftArguments = stack.slice(0, left),
 | 
					  let leftArguments = stack.slice(0, left),
 | 
				
			||||||
      rightArguments = stack.slice(left + 1);
 | 
					      rightArguments = stack.slice(left + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return operators[op].fn(...leftArguments, ...rightArguments);
 | 
					  return fixFloat(operators[op].fn(...leftArguments, ...rightArguments));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -315,4 +329,16 @@ const replaceConstants = expression => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					  * Fixes JavaScript's floating point precisions - Issue #5
 | 
				
			||||||
 | 
					  *
 | 
				
			||||||
 | 
					  * @param {Number} number
 | 
				
			||||||
 | 
					  *        The number to fix
 | 
				
			||||||
 | 
					  * @return {Number}
 | 
				
			||||||
 | 
					  *         Fixed number
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					const fixFloat = number => {
 | 
				
			||||||
 | 
					  return +number.toFixed(15);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Equation;
 | 
					export default Equation;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,5 +88,15 @@ export default {
 | 
				
			|||||||
    fn: Math.cot,
 | 
					    fn: Math.cot,
 | 
				
			||||||
    format: '10',
 | 
					    format: '10',
 | 
				
			||||||
    precedence: -1
 | 
					    precedence: -1
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  'round': {
 | 
				
			||||||
 | 
					    fn: Math.round,
 | 
				
			||||||
 | 
					    format: '10',
 | 
				
			||||||
 | 
					    precedence: -1
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  'floor': {
 | 
				
			||||||
 | 
					    fn: Math.floor,
 | 
				
			||||||
 | 
					    format: '10',
 | 
				
			||||||
 | 
					    precedence: -1
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,13 @@ describe('Basic math operators', () => {
 | 
				
			|||||||
  it('should work for multi-digit numbers', () => {
 | 
					  it('should work for multi-digit numbers', () => {
 | 
				
			||||||
    expect(M.solve('12+15')).to.equal(27);
 | 
					    expect(M.solve('12+15')).to.equal(27);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should deal with floating precision of javascript - #5', () => {
 | 
				
			||||||
 | 
					    expect(M.solve('0.2 + 0.1')).to.equal(0.3);
 | 
				
			||||||
 | 
					    expect(M.solve('0.2 + 0.4')).to.equal(0.6);
 | 
				
			||||||
 | 
					    expect(M.solve('round(floor(1.23456789/0.2)) * 0.2')).to.equal(1.2);
 | 
				
			||||||
 | 
					    expect(M.solve('1.23456789 - (1.23456789 % 0.2)')).to.equal(1.2);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Negative Numbers', () => {
 | 
					describe('Negative Numbers', () => {
 | 
				
			||||||
@@ -60,11 +67,11 @@ describe('Precedence', () => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Functions', () => {
 | 
					describe('Functions', () => {
 | 
				
			||||||
  it('should work for with parantheses', () => {
 | 
					  it('should work with parantheses', () => {
 | 
				
			||||||
    expect(M.solve('lg(4) * 5')).to.equal(10);
 | 
					    expect(M.solve('lg(4) * 5')).to.equal(10);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should work for without parantheses', () => {
 | 
					  it('should work without parantheses', () => {
 | 
				
			||||||
    expect(M.solve('lg4 * 5')).to.equal(10);
 | 
					    expect(M.solve('lg4 * 5')).to.equal(10);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user