Kai Noack - 3 months ago 13

Javascript Question

I have set up a js / three.js program for calculating cylinders by only two given values.

The only calculation that is quite difficult happens when **volume** and **surface** are given. From both values I need to calculate the radius or the height.

To recall the formulas:

Volume V = π·r²·h

Surface A = 2·π·r·(r+h)

If you do the math, you will get the cubic formula:

`0 = r^3 + A/(-2*pi)*r + V/pi`

which I honestly could not solve, so I used wolframalpha that gives this result for the radius r:

Note: There are three formulas for r, this is the first of them. See wolframalpha.

By trying to implement this equation in Javascript I realized that the radicand of

`√(54πV^2 - A^3)`

`NaN`

This leads to my question: How can I overcome the

`NaN`

`*(-1)`

I am a bit lost here, this is the first time I have to defeat NaN :-)

Thanks in advance for all your tips, advices, solutions and code.

Edit (reaching the goal): Is someone living on this earth feasable of solving the three equations in Javascript and can post his code? I have generally googled "calculate cylinder by surface and volume" and it seems nobody has done it before...

Answer

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

- JavaScript does not handle natively, and
- 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:

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

That term is an expression of `A`

and `V`

, so lets move that to a function of `A`

and `V`

, called `f`

So substitute that term for our new function `f`

, and now the roots are a lot more manageable:

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.