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:
parent
75969a4e1a
commit
2b3c06c413
36
dist/index.js
vendored
36
dist/index.js
vendored
@ -72,7 +72,11 @@ var Equation = {
|
||||
var stack = parseExpression(expression);
|
||||
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()) {
|
||||
// grouped variables like (y) need to have their parantheses removed
|
||||
variables.push(_.removeSymbols(a));
|
||||
@ -161,22 +165,30 @@ var parseExpression = function parseExpression(expression) {
|
||||
}
|
||||
|
||||
// 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;
|
||||
} else if (record.length) {
|
||||
stack.push(record, cur);
|
||||
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] === '-') {
|
||||
var beforeSign = stack[stack.length - 2];
|
||||
var beforeSign = stack[past - 1];
|
||||
|
||||
// 2 / -5 is OK, pass
|
||||
if (_operators2['default'][beforeSign]) {
|
||||
stack[past] += cur;
|
||||
|
||||
// (2+1) - 5 becomes (2+1) + -5
|
||||
} else if (beforeSign === ')') {
|
||||
stack[past] = '+';
|
||||
stack.push('-' + cur);
|
||||
|
||||
// 2 - 5 is also OK, pass
|
||||
} else if (_.isNumber(beforeSign)) {
|
||||
stack.push(cur);
|
||||
} else {
|
||||
@ -353,7 +365,7 @@ var evaluate = function evaluate(stack) {
|
||||
var leftArguments = stack.slice(0, left),
|
||||
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;
|
||||
module.exports = exports['default'];
|
10
dist/operators.js
vendored
10
dist/operators.js
vendored
@ -109,6 +109,16 @@ exports['default'] = {
|
||||
fn: Math.cot,
|
||||
format: '10',
|
||||
precedence: -1
|
||||
},
|
||||
round: {
|
||||
fn: Math.round,
|
||||
format: '10',
|
||||
precedence: -1
|
||||
},
|
||||
floor: {
|
||||
fn: Math.floor,
|
||||
format: '10',
|
||||
precedence: -1
|
||||
}
|
||||
};
|
||||
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 () {
|
||||
_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 () {
|
||||
@ -67,11 +74,11 @@ describe('Precedence', 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);
|
||||
});
|
||||
|
||||
it('should work for without parantheses', function () {
|
||||
it('should work without parantheses', function () {
|
||||
_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 variables = [];
|
||||
|
||||
stack.forEach(a => {
|
||||
stack.forEach(function varCheck(a) {
|
||||
if (Array.isArray(a)) {
|
||||
return a.forEach(varCheck);
|
||||
}
|
||||
|
||||
if (typeof a === 'string' && !_.isNumber(a) &&
|
||||
!operators[a] && a === a.toLowerCase()) {
|
||||
// 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
|
||||
if (!_.isNumber(cur) && !operators[cur] && cur !== '.') {
|
||||
if (!_.isNumber(cur) && !operators[cur] && cur !== '.' &&
|
||||
cur !== '(' && cur !== ')') {
|
||||
|
||||
record += cur;
|
||||
} else if (record.length) {
|
||||
stack.push(record, cur);
|
||||
record = '';
|
||||
} else if (_.isNumber(stack[stack.length - 1]) &&
|
||||
} else if (_.isNumber(stack[past]) &&
|
||||
(_.isNumber(cur) || cur === '.')) {
|
||||
|
||||
stack[stack.length - 1] += cur;
|
||||
} else if (stack[past] === '-') {
|
||||
const beforeSign = stack[stack.length - 2];
|
||||
stack[past] += cur;
|
||||
|
||||
// negation sign
|
||||
} else if (stack[past] === '-') {
|
||||
const beforeSign = stack[past - 1];
|
||||
|
||||
// 2 / -5 is OK, pass
|
||||
if (operators[beforeSign]) {
|
||||
stack[past] += cur;
|
||||
|
||||
// (2+1) - 5 becomes (2+1) + -5
|
||||
} else if (beforeSign === ')') {
|
||||
stack[past] = '+';
|
||||
stack.push(`-${cur}`);
|
||||
|
||||
// 2 - 5 is also OK, pass
|
||||
} else if (_.isNumber(beforeSign)) {
|
||||
stack.push(cur);
|
||||
} else {
|
||||
@ -272,12 +285,13 @@ const evaluate = stack => {
|
||||
if (!op) {
|
||||
return stack[0];
|
||||
}
|
||||
|
||||
const { left } = formatInfo(op);
|
||||
|
||||
let leftArguments = stack.slice(0, left),
|
||||
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;
|
||||
|
@ -88,5 +88,15 @@ export default {
|
||||
fn: Math.cot,
|
||||
format: '10',
|
||||
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', () => {
|
||||
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', () => {
|
||||
@ -60,11 +67,11 @@ describe('Precedence', () => {
|
||||
});
|
||||
|
||||
describe('Functions', () => {
|
||||
it('should work for with parantheses', () => {
|
||||
it('should work with parantheses', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user