How to overcome the NaN and continue the calculation in Javascript - Negative value for radicand of sqrt

So, discarding negative radicands is not the best solution because you still might rule out valid real solutions, since the radicands in the second term could cancel out the imaginary part from the first term. Additionally, the 2nd and 3rd roots have i in their formula, so you are sort of forced to deal with complex numbers there. These roots should also never be thrown out, because even for cubics with 3 real roots, 2 of the 3 roots are still calculated using complex numbers!

Dealing with complex numbers is something that

  1. JavaScript does not handle natively, and
  2. is non-trivial enough that you would not want to implement it yourself. That's where. math.js comes in.

Read here to learn about math.js. But for this question, you just need to know about one method. math.js does its work through its math object, and the method we are concerned with is math.eval(expr,scope) which will evaluate a string expression expr and use the variable assignments specified in scope.

So, initially looking at the 3 roots provided by wolfram:

roots

They are a little bit unwieldy. Upon closer inspection, they all have a common term:

common term

That term is an expression of A and V, so lets move that to a function of A and V, called f

f

So substitute that term for our new function f, and now the roots are a lot more manageable:

substituted with f

So, lets gets started. You just need to include math.js at the top of your project:

<script type="text/javascript" language="JavaScript" 
src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/0.26.0/math.min.js"></script>

Onto the script. First, define the f function described above:

        var f = function(A, V) {
            var scope = {A: A, V: V};
            var expr = math.eval(
                    '(sqrt(6) pi^(3/2) sqrt(54 pi V^2-A^3)-18 pi^2 V)^(1/3)'
                     ,scope);
            return expr;
        };

Note that: spaces implicitly mean to multiply terms, ie a b=a*b, and that the cube root of a number n is equivalent to n^(1/3)

So f will evaluate our expr using the arguments A and V for area and volume.

Now we can use that to define functions that will generate the 3 roots, r1, r2, and r3, given any area A and volume V

        var r1 = function(A, V) {
            var scope = {A: A, V: V, f: f(A, V)};
            var expr = math.eval(
                    'A/(6^(1/3) f)+f/(6^(2/3) pi)'
                    , scope);
            return expr;
        };

        var r2 = function(A, V) {
            var scope = {A: A, V: V, f: f(A, V)};
            var expr = math.eval(
                    '-((1+i sqrt(3)) A)/(2*6^(1/3) f) - ((1-i sqrt(3)) f)/(2*6^(2/3) pi)'
                    , scope);
            return expr;
        };

        var r3 = function(A, V) {
            var scope = {A: A, V: V, f: f(A, V)};
            var expr = math.eval(
                    '-((1-i sqrt(3)) A)/(2*6^(1/3) f) - ((1+i sqrt(3)) f)/(2*6^(2/3) pi)'
                    , scope);
            return expr;
        };

So now, lets test it out. Using the values from the link you provided, say the radius r is 2, and the height h is 1.5

Then, the volume V=pi*r^2 is approximately 18.85, and the surface area A=2pi*r(r+h) is approximately 43.982. Using the methods defined above, we can get the roots.

Note that result is the result of evaluating r^3 + A/(-2*pi)*r + V/pi using the given root, so if the result is 0, the root was calculated correctly. Actual values will be accurate to about ~15 digits due to round off error.

var A, V, r, scope;
A = 43.982, V = 18.85;

        //test r1
        r = r1(A, V);

        scope = {A: A, V: V, r: r};
        console.log('r1', r, 'result: ',math.eval('r^3+A/(-2pi) r+V/pi', scope));
        //r1 1.9999528096882697 - 2.220446049250313e-16i result: 4.440892098500626e-15 - 1.1101077869995534e-15i
        //round to 5 decimals:
        console.log('rounded r1:', math.round(r,5), 'rounded result: ',math.round(math.eval('r^3+A/(-2pi) r+V/pi', scope),5));
        //rounded r1:1.99995 rounded result: 0


        //test r2
        r = r2(A, V);

        scope = {A: A, V: V, r: r};
        console.log('r2', r,'result: ', math.eval('r^3+A/(-2pi) r+V/pi', scope));
        //r2 -2.9999999737884457 - 1.6653345369377348e-16i result: 2.6645352591003757e-15 - 8.753912513083332e-15i
        //round to 5 decimals:
        console.log('rounded r2:', math.round(r,5),'rounded result: ', math.round(math.eval('r^3+A/(-2pi) r+V/pi', scope),5));
        //rounded r2: -3 rounded result: 0

        //test r3
        r = r3(A, V);

        scope = {A: A, V: V, r: r};
        console.log('r3', r, 'result: ',math.eval('r^3+A/(-2pi) r+V/pi', scope));
        //r3 1.000047164100176 + 4.440892098500626e-16i result: -1.7762101637478832e-15i
        //round to 5 decimals
        console.log('rounded r3:', math.round(r,5), 'rounded result: ',math.round(math.eval('r^3+A/(-2pi) r+V/pi', scope),5));
        //rounded r3: 1.00005 rounded result: 0

And this agrees with the roots provided by wolfram alpha. {-3,1.00005,1.99995}

Also note that most of the results of console.log() of math.js objects will log the entire object, something like this:

r1 Complex { re=1.9999528096882697, im=-2.220446049250313e-16, toPolar=function(), more...} 

So I applied a toString() to the results I included, for readability.

Tags:

Javascript

Nan