Merge branch 'gh-pages'

This commit is contained in:
Mahdi Dibaiee 2015-06-18 17:31:14 +04:30
commit adb74a6f31
9 changed files with 210 additions and 37 deletions

View File

@ -18,8 +18,10 @@ console.log(Equation.solve('4 * lg(4) ^ 3')); // 32
// equation // equation
let sphereArea = Equation.equation('4 * PI * r^2'); let sphereArea = Equation.equation('4 * PI * r^2');
console.log(sphereArea(5)); // 314.1592653589793 console.log(sphereArea(5)); // 314.1592653589793
let test = Equation.equation('2x + 6y');
console.log(test(4, 3)).to.equal(8 + 18);
``` ```
You can also register your own operators and constants. You can also register your own operators and constants.

93
dist/index.js vendored
View File

@ -70,10 +70,14 @@ var Equation = {
*/ */
equation: function equation(expression) { equation: function equation(expression) {
var stack = parseExpression(expression); var stack = parseExpression(expression);
console.log(stack);
var variables = []; var variables = [];
stack.forEach(function (a) { stack.forEach(function varCheck(a) {
if (typeof a === 'string' && !_.isNumber(a) && !_operators2['default'][a] && a === a.toLowerCase()) { if (Array.isArray(a)) {
return a.forEach(varCheck);
}
if (isVariable(a)) {
// 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));
} }
@ -84,15 +88,30 @@ var Equation = {
args[_key] = arguments[_key]; args[_key] = arguments[_key];
} }
expression = expression.replace(/[a-z]*/g, function (a) { stack.forEach(function varCheck(a, i, arr) {
if (Array.isArray(a)) {
return a.forEach(varCheck);
}
var index = variables.indexOf(a); var index = variables.indexOf(a);
if (index > -1) { if (index > -1) {
return args[index] || 0; // grouped variables like (y) need to have their parantheses removed
arr[i] = args[index];
} }
return a;
}); });
return Equation.solve(expression); stack = sortStack(stack);
stack = _.parseNumbers(stack);
stack = solveStack(stack);
// expression = expression.replace(/[a-z]*/g, a => {
// let index = variables.indexOf(a);
// if (index > -1) {
// return args[index] || 0;
// }
// return a;
// });
return stack;
}; };
}, },
@ -150,33 +169,50 @@ var MIN_PRECEDENCE = Math.min.apply(Math, _toConsumableArray(PRECEDENCES));
var parseExpression = function parseExpression(expression) { var parseExpression = function parseExpression(expression) {
var stream = new _ReadStream2['default'](expression), var stream = new _ReadStream2['default'](expression),
stack = [], stack = [],
record = ''; record = '',
cur = undefined,
past = undefined;
// Create an array of separated numbers & operators // Create an array of separated numbers & operators
while (stream.next()) { while (stream.next()) {
var cur = stream.current(), cur = stream.current();
past = stack.length - 1; past = stack.length - 1;
if (cur === ' ') { if (cur === ' ') {
continue; continue;
} }
// 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) {
var beforeRecord = past - (record.length - 1);
if (isVariable(record) && _.isNumber(stack[beforeRecord])) {
stack.push('*');
}
stack.push(record, cur); stack.push(record, cur);
record = ''; record = '';
} else if (_.isNumber(stack[stack.length - 1]) && (_.isNumber(cur) || cur === '.')) {
stack[stack.length - 1] += cur; // numbers and decimals
} else if (_.isNumber(stack[past]) && (_.isNumber(cur) || 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 {
@ -187,6 +223,10 @@ var parseExpression = function parseExpression(expression) {
} }
} }
if (record.length) { if (record.length) {
var beforeRecord = past - (record.length - 1);
if (isVariable(record) && _.isNumber(stack[beforeRecord])) {
stack.push('*');
}
stack.push(record); stack.push(record);
} }
@ -353,7 +393,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 +453,28 @@ 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);
};
/**
* Recognizes variables such as x, y, z
* @param {String} a
* The string to check for
* @return {Boolean}
* true if variable, else false
*/
var isVariable = function isVariable(a) {
return typeof a === 'string' && !_.isNumber(a) && !_operators2['default'][a] && a === a.toLowerCase();
};
exports.isVariable = isVariable;
exports['default'] = Equation; exports['default'] = Equation;
module.exports = exports['default'];

10
dist/operators.js vendored
View File

@ -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'];

View File

@ -30,6 +30,11 @@ describe('Equations', function () {
_expect.expect(equation).to['throw'](); _expect.expect(equation).to['throw']();
}); });
it('should work with NumVariable expressions like 2x', function () {
var equation = _M2['default'].equation('2x + 6y');
_expect.expect(equation(4, 3)).to.equal(8 + 18);
});
it('Test case', function () { it('Test case', function () {
var equation = _M2['default'].equation('2+x*(y+4)+z^2'); var equation = _M2['default'].equation('2+x*(y+4)+z^2');
_expect.expect(equation(2, 4, 3)).to.equal(27); _expect.expect(equation(2, 4, 3)).to.equal(27);

11
dist/tests/solve.js vendored
View File

@ -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);
}); });

View File

@ -47,26 +47,44 @@ let Equation = {
*/ */
equation(expression) { equation(expression) {
let stack = parseExpression(expression); let stack = parseExpression(expression);
console.log(stack);
let variables = []; let variables = [];
stack.forEach(a => { stack.forEach(function varCheck(a) {
if (typeof a === 'string' && !_.isNumber(a) && if (Array.isArray(a)) {
!operators[a] && a === a.toLowerCase()) { return a.forEach(varCheck);
}
if (isVariable(a)) {
// 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));
} }
}); });
return function(...args) { return function(...args) {
expression = expression.replace(/[a-z]*/g, a => { stack.forEach(function varCheck(a, i, arr) {
if (Array.isArray(a)) {
return a.forEach(varCheck);
}
let index = variables.indexOf(a); let index = variables.indexOf(a);
if (index > -1) { if (index > -1) {
return args[index] || 0; // grouped variables like (y) need to have their parantheses removed
arr[i] = args[index];
} }
return a;
}); });
return Equation.solve(expression); stack = sortStack(stack);
stack = _.parseNumbers(stack);
stack = solveStack(stack);
// expression = expression.replace(/[a-z]*/g, a => {
// let index = variables.indexOf(a);
// if (index > -1) {
// return args[index] || 0;
// }
// return a;
// });
return stack;
}; };
}, },
@ -114,34 +132,51 @@ const MIN_PRECEDENCE = Math.min(...PRECEDENCES);
const parseExpression = expression => { const parseExpression = expression => {
let stream = new ReadStream(expression), let stream = new ReadStream(expression),
stack = [], stack = [],
record = ''; record = '',
cur, past;
// Create an array of separated numbers & operators // Create an array of separated numbers & operators
while (stream.next()) { while (stream.next()) {
const cur = stream.current(), cur = stream.current();
past = stack.length - 1; past = stack.length - 1;
if (cur === ' ') { if (cur === ' ') {
continue; continue;
} }
// 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) {
let beforeRecord = past - (record.length - 1);
if (isVariable(record) && _.isNumber(stack[beforeRecord])) {
stack.push('*');
}
stack.push(record, cur); stack.push(record, cur);
record = ''; record = '';
} else if (_.isNumber(stack[stack.length - 1]) &&
// numbers and decimals
} 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 {
@ -152,6 +187,10 @@ const parseExpression = expression => {
} }
} }
if (record.length) { if (record.length) {
let beforeRecord = past - (record.length - 1);
if (isVariable(record) && _.isNumber(stack[beforeRecord])) {
stack.push('*');
}
stack.push(record); stack.push(record);
} }
@ -272,12 +311,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 +355,28 @@ 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);
};
/**
* Recognizes variables such as x, y, z
* @param {String} a
* The string to check for
* @return {Boolean}
* true if variable, else false
*/
export const isVariable = a => {
return typeof a === 'string' && !_.isNumber(a) &&
!operators[a] && a === a.toLowerCase();
};
export default Equation; export default Equation;

View File

@ -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
} }
}; };

View File

@ -23,6 +23,11 @@ describe('Equations', () => {
expect(equation).to.throw(); expect(equation).to.throw();
}); });
it('should work with NumVariable expressions like 2x', () => {
let equation = M.equation('2x + 6y');
expect(equation(4, 3)).to.equal(8 + 18);
});
it('Test case', () => { it('Test case', () => {
let equation = M.equation('2+x*(y+4)+z^2'); let equation = M.equation('2+x*(y+4)+z^2');
expect(equation(2, 4, 3)).to.equal(27); expect(equation(2, 4, 3)).to.equal(27);

View File

@ -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);
}); });