TomFrost / Jexl

Javascript Expression Language: Powerful context-based expression parser and evaluator
MIT License
561 stars 93 forks source link

Floating point innaccuracy when adding two numbers #24

Closed erotavlas closed 7 years ago

erotavlas commented 7 years ago

When calculating the addition of two numbers

jexl.eval(that.formula, that.context, function (err, res) {
     console.log(res);
});

where that.formula is a+b

when the context is

{
a: 1.2,
b: 1.22
}

the result is 2.42

when the context is

{
a: 1.2,
b: 1.222
}

the result is 2.4219999999999997

and when I increase b by one more decimal place

{
a: 1.2,
b: 1.2222
}

the result is 2.4222

TomFrost commented 7 years ago

Unfortunately, there's nothing Jexl can do about this -- the behavior you're seeing is native to Node, and also native to most floating point math operations on an x86 processor. It's kind of crazy how obviously wrong it is to us humans, but with how the x86 works, that's just how it comes out. Regardless of the programming language you use, it's best to convert your significant figures to integers and handle the decimal superficially.

$ node
> 1.2+1.222
2.4219999999999997
$ python
Python 2.7.6 (default, Oct 26 2016, 20:30:19)
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 1.2 + 1.222
2.4219999999999997
$ irb
irb(main):001:0> 1.2 + 1.222
=> 2.4219999999999997

(oddly enough, PHP autocorrects for decimal math. Point for PHP.) (I got curious and tried it in Go as well -- Go does the correction automatically too!)

erotavlas commented 7 years ago

Is it possible to overload the math operators to use operations from another library (such as BigNumber.js , Big.js, or, Decimal.js) I was reading somewhere that this library evaluates to the correct number of decimal places. https://stackoverflow.com/a/27251971/1462656

EDIT - I tested those libraries and they produce the expected values.

TomFrost commented 7 years ago

@erotavlas: You certainly can! jexl.addBinaryOp(str, (left, right) => { ... })) is exactly what you need. While the intention for this call is to add new operators to the Jexl grammar, you can use it to overwrite existing ones like + and -. For reference, here is a list of all built in unary and binary operators: https://github.com/TechnologyAdvice/Jexl/blob/2.0/lib/grammar.js

Though, as a performance suggestion, I'd shy away from overwriting + and - et. al. because doing complex calculation for every arithmetic operation would make evaluations unnecessarily heavy. It might be worth it to add operators like +. and -. to denote decimal-sensitive arithmetic instead, and only use them when you know you need them.