jiggzson / nerdamer

a symbolic math expression evaluator for javascript
http://www.nerdamer.com
MIT License
517 stars 82 forks source link

Replacing mathjs math.eval() and math.format() with Nerdamer to calculate values #521

Closed q2apro closed 4 years ago

q2apro commented 4 years ago

I am in the middle of replacing mathjs with nerdamer, especially the evaluation of values.

In MathJS (docs):

var line = "45/12+4";
var answer = math.eval(line, scope);  // scope is {}
var answer_out = math.format( answer, {precision: 7, lowerExp: -10, upperExp: 10} );

In Nerdamer I am trying to do:

var line = "45/12+4";
var answer = nerdamer(line).evaluate();
console.log('#answers (values): '+answer.toString()); // result: 31/4
console.log('#answers (decimal values): '+answer.toTeX('decimal')); // result: 7.75

Is answer.toTeX('decimal') really the correct way to get the decimal value?

And besides, why does answer.length yield an undefined? ... When I use answer = nerdamer.solveEquations(line); I get always an int for answer.length. I need the answer count to iterate over the results.

Happypig375 commented 4 years ago

why does answer.length yield an undefined?

What would you expect to be an answer to that? 45/12+4 indeed simplifies to 31/4, which is a mathematical expression, not "a set of solutions" per se.

Happypig375 commented 4 years ago

If you are sure that the evaluated result is a vector (also used as a list), you can do:

nerdamer("[x^6,2,3]").symbol.elements.length // 3
nerdamer("[x^6,2,3]").symbol.elements[0].toString() // "x^6"
q2apro commented 4 years ago

why does answer.length yield an undefined?

What would you expect to be an answer to that? 45/12+4 indeed simplifies to 31/4, which is a mathematical expression, not "a set of solutions" per se.

I am using var answer also for the result from nerdamer.solveEquations(line);. But yes, I check now for an equation then use the one value (from the evaluation) or the values (from the equation solving).

Good to know this one, thanks: nerdamer("[x^6,2,3]").symbol.elements.length // 3 👍

Still open is the question about hwo to replace the both mathjs functions above (eval and format).

Happypig375 commented 4 years ago
math.eval("22+33+44x", {x:4}) // 231
nerdamer("22+33+44x", {x:4}).text() // 231

math.format(math.eval("23/77", {}), {}) // 0.2987012987012987
nerdamer("23/77", {x:4}).text("decimals") // 0.2987012987012987
math.format(math.eval("23/77", {}), {precision: 7}) // 0.2987013
nerdamer("23/77", {x:4}).text("decimals", 7) // 0.298701

As for lowerExp and upperExp, I'm not sure what they do:

math.format(math.eval("23/77", {}), {lowerExp: 5, upperExp: 8}) // 2.987012987012987e-1

Doesn't seem to do much.

jiggzson commented 4 years ago

@q2apro, additionally

var n = nerdamer.size(nerdamer("[x^6,2,3]"));
console.log(n.toString())

However if you're iterating over the result then @Happypig375's solution is the best answer.

q2apro commented 4 years ago

Thanks!

Mhh, it seems not to work with roots?!

nerdamer("sqrt(19)").text("decimals", 5);

results in:

sqrt(19)

I expected 4,35889.

What's the issue?

jiggzson commented 4 years ago

You have to call evaluate. Evaluation of functions is delayed in order to provide algebraic results. This way you don't have to deal with those pesky floating point errors in something like sqrt(2)*sqrt(2). You'll get a nice even 2 instead of something like 2.0000000000000004.

nerdamer("sqrt(19)").evaluate().text("decimals", 6);

Also @Happypig375 created an issue regarding the second argument for text in #523.

q2apro commented 4 years ago

Nice. But one more example:

var line = "x^2-x+5=0";
var answer = nerdamer.solveEquations(line); // two results: (1/2)*i*sqrt(19)+1/2 and (-1/2)*i*sqrt(19)+1/2
var ans_string = answer[0].toString(); // result: (1/2)*i*sqrt(19)+1/2
var output = nerdamer(ans_string).evaluate().text("decimals", 6);
console.log(output); // result: 0.5+2.179449471770337*i

Issue: The value at "i" is not rounded by the given decimals.

jiggzson commented 4 years ago

I see. I'll take care of it.

q2apro commented 4 years ago

From:

var answer = nerdamer('solve(2*a^(2)+4*a*6=128, a)');
console.log(answer.toString());
console.log(answer);

I get as result:

image

I have one main function that goes over answer which is usually an array. In the case above, however, it is not. What would be the best (fool proof) way to handle this. Checking typeof and then making it an array? Thanks in advance!

jiggzson commented 4 years ago

Solve always returns an Expression which is wrapped around a Vector. The Expression class contains a method named each which can be used to iterate over the answers.

var answer = nerdamer('solve(2*a^(2)+4*a*6=128, a)');
answer.each(function(e) {
    var ans = e.text('decimals');
    console.log(ans);
});
q2apro commented 4 years ago

Thanks ❤️ This helped.

Here is my code in case it helps others too:

// can be different answer types
answer = nerdamer("gcd(15,60)").evaluate(); // most inputs
answer = nerdamer("realpart(81+5*b*i)"); // no evaluate() here
answer = nerdamer('solve(2*a^(2)+4*a*6=128, a)'); // no evaluate() here

// Expression, e.g. answer from solve(equ, var)
 if(answer.symbol && answer.symbol.elements.length>0)
{
    var ans_temp = [];
    // copy to answer array
    answer.each(function(elem) {
        ans_temp.push( elem.text('decimals') );
    });
    answer = ans_temp;
}

// is array 
for(var i=0; i<answer.length; i++)
{
    // limit results
    if(i>8)
    {
        answer_out += '…';
        break;
    }

    let ans_string = answer[i].toString();

    if(isNaN(ans_string))
    {
        ans_string += " = <b>"+nerdamer(ans_string).evaluate().text("decimals", $this.precision)+"</b>";
    }
    let answer_cnt = ( answer.length==1 ? 'x ' : 'x'+num_to_subnum(i+1) );
    answer_out += answer_cnt + ' = ' + ans_string + (i < answer.length-1 ? '<br>' : '');
}
jiggzson commented 4 years ago

@q2apro, awesome. Your code actually made me realize that there's a bug in the each method. It shouldn't iterate an empty set. Thanks for sharing.