问题
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)
is negative and Javascript is returning NaN
.
This leads to my question: How can I overcome the NaN
and continue the calculation - should I use complex numbers, how? What workarounds have you used? Can I just multiply the radicand by *(-1)
, remember this value and consider it later on?
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...
回答1:
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.
回答2:
use isNaN() to test if the value is NaN without errors
回答3:
Edit2: sorry I misread the original question, you cannot get a square root of a negative number in JS. Multiplying by -1 and then multiplying by 'i' (square root of -1) is the way it would be done in maths iirc, but that is not available in JS either.
Edit: Pi is Math.PI in Javascript, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math for all the possible functions and properties, including various constants.
You can't continue with calculations once you've got NaN, but you can find where you're getting NaN and fix.
You'll be wanting these:
parseInt(variablename, 10);
Converts the value of 'variablename' into an integer. The second argument is the radix, which here I've put as 10 as I'm assuming you're using decimals. If you didn't have this it might try to convert it depending on what it gets, so something like parseInt('08') is not the same as 8! More: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
the string '10px' will get changed to 10 by parseInt, but 'foo20' will return NaN.
parseFloat(variableName);
converts the value of 'variablename' into a floating point number.
来源:https://stackoverflow.com/questions/25329786/how-to-overcome-the-nan-and-continue-the-calculation-in-javascript-negative-va