'How to parse & process mathematical expressions in JavaScript Calculator?

Whats the best approach with javascript to evaluate an expression like 3+1*sin(20)*e^3?

First of all, I do not want to use built-in math-functions. In fact, I have already defined my own functions for power, square root, sin, cos, exponential, etc.

What I'm looking for, is a good way to parse an expression so I can pass them to these functions I created. I know there is an eval() function available in JavaScript for evaluating input expressions, but I'm not sure if that's suitable for this context.

Should I use eval()? If yes, how do I use it in this context? If no, what's a more suitable way to parse mathematical expressions?! It's not an school assignment, its just a doubt.



Solution 1:[1]

Using a third party library

In a real life situation, you'd want to use a library like math.js to do the calculations for you.

To use this library, first include it in your HTML :

 <script src='path/to/math.js'></script>

Then, you can just use the math.eval method to evaluate your expression :

math.eval('3+1*sin(20)*e^3');

That is, if 20 is a value expressed in radians. As it's probably a value expressed in degrees, you'd need to make this small adjustment :

math.eval('3+1*sin(20 deg)*e^3');

Because you explicitly said that you do not want to use the built-in math-functions, I suspect you're working on a school assignment. Note that submitting school assignments on StackOverflow is frowned upon, as it is considered a form of cheating.

But let's just ignore this and move on, shall we?!


Doing it on your own

Step 1 : Calculation with built-in math functions

In real life projects you should ALWAYS use built-in math functions or third party libraries (like math.js) that use built-in math functions under the hood. That's because built-in math functions are both 100% reliable and performance optimized.

It's impossible to create your own equivalent of built-in math functions in a way that is both as reliable and as performance optimized as the built-in functions, so there is NEVER a good reason NOT to use the built-in functions. But since you explicitly asked for it, let's ignore that and look at how to do things WITHOUT the built-in math functions. I assume this also implied you do not want to use math.js or any other library.

Using JavaScript's built-in functions, you'd calculate your expression like this :

3+1*Math.sin(20)*Math.pow(Math.E,3)

Built-in functions used :

Here, again, it's important to state this is true only if 20 is a value in radians. As 20 is probably a value in degrees, you probably want to define your own degree based sin function like this :

var dsin = function() {
  var piRatio = Math.PI / 180;
  return function dsin(degrees) {
    return Math.sin(degrees * piRatio);
  };
}();

Now, replace Math.sin with dsin

3+1*dsin(20)*Math.pow(Math.E,3)

In any real world application, this would be the best was to calculate 3+1*sin(20)*e^3!

Step 2 : Replacting built-in math functions with your own

Now, as you REALLY, REALLY, REALLY seem to want to go without built-in functions, just the next step is to replace every built-in function with a self-written equivalent. Often, there are multiple mathematical paths towards the same goal.

For example, you could write your pow function recursively like this :

var pow = function(base, exp) {
  if (exp == 0)
    return 1;
  else
    return base * power(base, exp - 1);
}

An iterative implementation :

var pow = function(base, exp) {
  var result = 1;
  while(exp--) {
    result *= base;
  }
  return result;
}

So now what, you ask?! How can you implement dsin without relying on Math.sin, you ask?!

Well... A little bit of research tells you that you can calculare sin, using the formula sin(x) = x - x^3 / 3! + x^5 / 5! - x^7 / 7! + x^9 / 9! - ...

In JavaScript, that would be something like this :

var sin = function(x) {
    var t1 = -1,t2 = 3,v1 = x,v2 = v1 + 1,it = 0;
    while (it < 10) {
        v2 = v1 + (t1 * pow(x, t2) / function(n){
        j = 1;
        for(i=1;i<=n;i++)
            j = j*i;
            return j;
        }(t2));
        t1 = -1 * t1;
        t2 += 2;
        v1 = v2;
        it++;
    }
    return v2;
}

You might notice that I put my factorial calculation in a lambda function. A more elegant solution would be to put your lambda function in a separate function, as you're likely to need it elsewhere.

Now, you just need to puzzle all the pieces together and wrap them in a nice object :

var Calc = (function(){
    return {
        E : 2.718281828459045,
        pow : function (base, exp) {
            var result = 1;
            while (exp--) {
                result *= base;
            }
            return result;
        },
        fact : function(n){
            j = 1;
            for(i=1;i<=n;i++)
                j = j*i;
            return j;
        },
        sin : function(x) {
            var t1 = -1,t2 = 3,v1 = x,v2 = v1 + 1,it = 0;
            while (it < 10) {
                v2 = v1 + (t1 * this.pow(x, t2) / this.fact(t2));
                t1 = -1 * t1;
                t2 += 2;
                v1 = v2;
                it++;
            }
            return v2;
        },
        dsin : function() {
            var piRatio = Math.PI / 180;
            return function dsin(degrees) {
                return Math.sin(degrees * piRatio);
            };
        }()
    };
})();

You can use the methods of these objects like this :

3+1*Calc.dsin(20)*Calc.pow(Calc.E,3)

Step 3 : Parsing

If you actually want your application to read an expression like 3+1*sin(20)*e^3 directly, you should NOT use eval(). Instead, you should write your own parser.

The proper way to do this, is to first write a tokenizer that converts your expression to a syntax tree. Then, you pass your syntax tree to an interpreter that processes your syntax tree by running the calculation functions you've created in step 1.

For an in depth look into how to build a syntax tree and interpreter, you could take a look at this tutorial.

Solution 2:[2]

I wrote a parser that does what you need. It's called SwanJS and it doesn't use eval or new Function.

Installation:

npm install @onlabsorg/swan-js

Usage:

const swan = require('@onlabsorg/swan-js');  // It works also in the browser
const evaluate = swan.parse("3 + 1 * sin(20) * e**3");
const context = swan.createContext({
   sin: Math.sin,  // or your own sin function
   e: Math.E
});
const value = await evaluate(context);

If you don't like ** as power operator, you may change it, but that's not well documented. Look at /lib/interpreter.js to see how to define a new grammar or ask me if you prefer.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Marcello Del Buono