Merge branch 'gh-pages'
This commit is contained in:
commit
adb74a6f31
@ -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.
|
||||||
|
91
dist/index.js
vendored
91
dist/index.js
vendored
@ -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
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'];
|
5
dist/tests/equation.js
vendored
5
dist/tests/equation.js
vendored
@ -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
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
94
lib/index.js
94
lib/index.js
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user