decimal.js-extensions-evaluate
v0.1.0
Published
A decimal.js extension for the evaluation of arithmetic expressions
Downloads
273
Maintainers
Readme
A decimal.js extension to
evaluate arithmetic expressions
This is a lightweight expression parser. See math.js for a much more powerful and full-featured expression parser and math library.
Usage example
import Decimal from 'decimal.js';
import { evaluate } from 'decimal.js-extensions-evaluate';
evaluate.extend(Decimal);
let result = Decimal.evaluate('0.1 + 0.2');
console.log(result); // '0.3'
console.log(result instanceof Decimal); // true
Decimal.precision = 30;
result = Decimal.eval('2sin(x)', { x: 3, sin: n => n.sin() });
console.log(result); // '0.282240016119734444201489605616'
console.log(Decimal.eval.expression); // '2*sin(x)'
Test
$ npm test
$ node test/eval "cos(1 + sin(0.5))"
Licence
Credits
The implementation is derived from the article Top Down Operator Precedence by Douglas Crockford, available at
http://crockford.com/javascript/tdop/tdop.html.
Also see:
https://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing
http://effbot.org/zone/simple-top-down-parsing.htm
Scope example
const scope = {
NaN,
Infinity,
// Constants to 40 d.p.
E: '2.7182818284590452353602874713526624977572',
LN2: '0.6931471805599453094172321214581765680755',
LN10: '2.3025850929940456840179914546843642076011',
LOG2E: '1.4426950408889634073599246810018921374266',
LOG10E: '0.4342944819032518276511289189166050822944',
PI: '3.1415926535897932384626433832795028841972',
SQRT1_2: '0.7071067811865475244008443621048490392848',
SQRT2: '1.4142135623730950488016887242096980785697',
PHI: '1.6180339887498948482045868343656381177203',
abs: x => x.abs(),
acos: x => x.acos(),
acosh: x => x.acosh(),
asin: x => x.asin(),
asinh: x => x.asinh(),
atan: x => x.atan(),
atanh: x => x.atanh(),
atan2: (y, x) => Decimal.atan2(y, x),
cbrt: x => x.cbrt(),
ceil: x => x.ceil(),
cos: x => x.cos(),
cosh: x => x.cosh(),
exp: x => x.exp(),
floor: x => x.floor(),
ln: x => x.ln(),
log: (x, y) => x.log(y),
log10: x => Decimal.log10(x),
log2: x => Decimal.log2(x),
max: (...args) => Decimal.max(...args),
min: (...args) => Decimal.min(...args),
random: (n) => Decimal.random(+n),
round: x => x.round(),
sign: x => Decimal.sign(x),
sin: x => x.sin(),
sinh: x => x.sinh(),
sqrt: x => x.sqrt(),
tan: x => x.tan(),
tanh: x => x.tanh(),
trunc: x => x.trunc(),
};
result = Decimal.eval('3.4564564645E + 4(PHI - cos(LN2) + exp(PI)) / 2.003e+12', scope);
console.log(result); // '9.39562279835805409202716773322'
console.log(Decimal.eval.expression); // '3.4564564645*E+4*(PHI-cos(LN2)+exp(PI))/2.003e+12'
Notes
The results of operations are calculated to the number of significant digits specified by the value of Decimal.precision
and rounded using the rounding mode of Decimal.rounding
.
Decimal.precision = 5;
Decimal.eval('1/3'); // '0.33333'
The comparison operators return 1
for true
and 0
for false
.
Decimal.eval('2 > 3'); // '0'
As with JavaScript, the logical operators &&
and ||
return one of their operands.
Decimal.eval('2 && 3'); // '3'
Exponential notation is supported.
Decimal.eval('1.234e+5 == 123400'); // '1'
As shown above, user-defined variables and functions are supported through a scope object passed as the second argument.
Decimal.eval('3sin(x)', { x: 3, sin: n => n.sin() }); // '0.4233600241796016663'
Once a scope is defined, it exists until it is replaced by a new scope.
Decimal.eval('x + y', { x: 0.1, y: 0.2 }); // '0.3'
Decimal.eval('2(x - 3y)'); // '1'
Decimal.eval('2xy'); // '0.04'
Decimal.eval('xy', {}); // 'Decimal.eval: unknown symbol: x'
Multiplication is implicit for immediately adjacent values, but note that multiplication and division are left-associative:
Decimal.eval('1/2x', { x: 4 }); // '2' (1/2)*x
Decimal.eval('1/(2x)'); // '0.125' 1/(2*x)
The evaluated expression is available at Decimal.eval.expression
.
Decimal.eval('2x(3x + 4y)');
Decimal.eval.expression; // '2*x*(3*x + 4*y)'
Each variable's value and each function's return value is passed to the Decimal
constructor, so a number, string or Decimal can be used in the definition in the scope object.
Decimal.eval('x % y + z', { x: 4, y: '3', z: new Decimal('2') }); // '3'
The values that are passed to functions are Decimal
values, so their prototype methods are available.
Decimal.eval('add(0.1, 0.2)', { add: (x, y) => x.plus(y) }); // '0.3'
The definition of a valid identifier for a variable or function is the same as for JavaScript except that the only non-ASCII characters allowed are the letters of the Unicode Greek and Coptic range: \u0370-\u03FF
.
I.e. an identifier must start with a letter, _
or $
, and any further characters must be letters, numbers, _
or $
.
Decimal.eval('2πr', { π: Math.PI, r: 1 }); // '6.283185307179586'
A variable or function's value can be changed without having to redefine the scope, by passing an object containing the updated definitions as the first and only argument.
Below, the value of π
is updated and the last expression '2πr'
is re-evaluated each time.
Decimal.eval({ π: '3.14159265358979323846' }); // '6.28318530717958647692'
Decimal.eval({ π: 3 }); // '6'
A new scope has not been created so r
is still available.
Decimal.eval('r'); // 1
Decimal.eval('r', {}); // 'Decimal.eval: unknown symbol: r'
When a new expression is passed to Decimal.eval
it is tokenized and those tokens are re-evaluated on each subsequent eval
call until a new expression is passed and a new set of tokens is created.
Decimal.eval('x^y', { x: 2, y: 3 }); // '8' 2^3
Decimal.eval({ y: -3 }); // '0.125' 2^-3
Decimal.eval({ x: 4 }); // '0.015625' 4^-3
Decimal.eval('1'); // '1'
Decimal.eval({ y: 4 }); // '1'
Decimal.eval('x^y'); // '256' 4^4
Note, though, that new variables or functions cannot be added via the updating object.
Decimal.eval({ z: 5 }); // 'Decimal.eval: identifier not in scope: z'