Hi Jim. > Unfortunately, this means that the names here > and the values assigned to them and the comment above them conflict. > If the variables could be named "p/3" and "q/2" then all would be clear, > but I don't know how to do that naming very easily. Perhaps the > comment could be simply reworded: > > // substitute <blah blah blah> > // x^3 + Px + Q = 0 > // Since we actually need P/3 and Q/2 for all of the > // calculations that follow, we will calculate > // p = P/3 > // q = Q/2 > // instead and use those values for simplicity of the code.
Good point. Done. > Line 1105 - single quotes in comments freaks out my version of > gnuemacs. > I usually try to avoid them, except in pairs, but there isn't a better > way to word this comment. :-( We could always go with "the method of Cardano", although that doesn't sound good at all. Or we could use a backtick instead of the single quote - it looks similar enough. > Lines 1157-1163 - the old code used to copy the eqn before it got > clobbered with roots. Here it is too late. You probably need to move > this code up near line 1135 before the 3 roots are stuffed into the > res array. (Did you test the eqn==res case?) You're right. I'm sorry about this. > I noticed that the "Casus irreducibilis" case isn't in Cordano's > method. > He only finds roots for the 1 and 2 root case and punts for 3 roots. > So, this is someone else's method. It would be nice to figure out who > or what and list a reference, even though the Graphics Gems and the > old > code didn't. The closest reference I can find is unattributed on > Wikipedia, but you could include it in a comment for reference: > > http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method Done. > Line 1133 - I don't understand why that term has -q in it. The above > link and the original code both computed essentially the arccos of > this > formula without the negation of q. ??? Since acos(-v) == pi - acos(v) > this would seem to negate the result and bias it by pi/3. Negating it > won't affect the eventual cosine, but the bias by pi/3 will. Am I > missing something? I think you are. What's going on is that our code is computing ret[0] = t2, ret[1] = t0, ret[2] = t1, where (t0, t1, t2 are the tk's from the wikipedia link). Let X = (3q/2p)*sqrt(-3/p) where p and q are the ones from the wikipedia article, not our code. So, when we do ret[0] = t * cos(phi), that's: = t * cos(acos(-X)) = t * cos(pi/3 - acos(X)) = t * cos(acos(X) - pi/3) = t * cos(acos(X) - pi/3 - pi) = t * cos(acos(X) - 2*2*pi/3) = t2 ret[0] and ret[1] are very similar to prove - you just add/subtract pi/3 from the argument to cos. I unfortunately don't have access to the icedtea servers at this moment, so I attached a patch. I hope that's ok. Regards, Denis.
--- dashing/2d/jdk/src/share/classes/java/awt/geom/CubicCurve2D.java 2010-12-24 10:01:45.378556843 -0500 +++ cc2d/2d/jdk/src/share/classes/java/awt/geom/CubicCurve2D.java 2010-12-24 11:01:42.205614715 -0500 @@ -1083,24 +1083,63 @@ * @since 1.3 */ public static int solveCubic(double eqn[], double res[]) { - // From Numerical Recipes, 5.6, Quadratic and Cubic Equations - double d = eqn[3]; - if (d == 0.0) { - // The cubic has degenerated to quadratic (or line or ...). + // From Graphics Gems: + // http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c + final double d = eqn[3]; + if (d == 0) { return QuadCurve2D.solveQuadratic(eqn, res); } - double a = eqn[2] / d; - double b = eqn[1] / d; - double c = eqn[0] / d; - int roots = 0; - double Q = (a * a - 3.0 * b) / 9.0; - double R = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0; - double R2 = R * R; - double Q3 = Q * Q * Q; - a = a / 3.0; - if (R2 < Q3) { - double theta = Math.acos(R / Math.sqrt(Q3)); - Q = -2.0 * Math.sqrt(Q); + /* normal form: x^3 + Ax^2 + Bx + C = 0 */ + final double A = eqn[2] / d; + final double B = eqn[1] / d; + final double C = eqn[0] / d; + + + // substitute x = y - A/3 to eliminate quadratic term: + // y^3 +Py + Q = 0 + // + // Since we actually need P/3 and Q/2 for all of the + // calculations that follow, we will calculate + // p = P/3 + // q = Q/2 + // instead and use those values for simplicity of the code. + + double sq_A = A * A; + double p = 1.0/3 * (-1.0/3 * sq_A + B); + double q = 1.0/2 * (2.0/27 * A * sq_A - 1.0/3 * A * B + C); + + /* use Cardano's formula */ + + double cb_p = p * p * p; + double D = q * q + cb_p; + + int num; + // XXX: we consider 0 to be anything within 1e-9 of 0. + // Is this really right? Maybe we should use a bound that changes + // with the number being tested (math.ulp would do that, but + // what input do we give it? And do we scale it, or will + // within(D, 0, math.ulp(somenumber)) be good enough). + if (iszero(D)) { + // XXX: do we really need iszero for q? All we do with it is + // take it's cube root, which works fine even for Double.MIN_VALUE + // Then again, if we remove it, we will get two extremely close + // roots for equations where there is only one zero root. + // We probably should use something like iszero, but much more + // scrict - so, within(q, 0, 2*Math.ulp(0)); + if (iszero(q)) { /* one triple solution */ + res[ 0 ] = 0; + num = 1; + } else { /* one single and one double solution */ + final double u = Math.cbrt(-q); + res[ 0 ] = 2*u; + res[ 1 ] = -u; + num = 2; + } + } else if (D < 0) { /* Casus irreducibilis: three real solutions */ + // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method + final double phi = 1.0/3 * Math.acos(-q / Math.sqrt(-cb_p)); + final double t = 2 * Math.sqrt(-p); + if (res == eqn) { // Copy the eqn so that we don't clobber it with the // roots. This is needed so that fixRoots can do its @@ -1108,24 +1147,41 @@ eqn = new double[4]; System.arraycopy(res, 0, eqn, 0, 4); } - res[roots++] = Q * Math.cos(theta / 3.0) - a; - res[roots++] = Q * Math.cos((theta + Math.PI * 2.0)/ 3.0) - a; - res[roots++] = Q * Math.cos((theta - Math.PI * 2.0)/ 3.0) - a; + + res[ 0 ] = ( t * Math.cos(phi)); + res[ 1 ] = (-t * Math.cos(phi + Math.PI / 3)); + res[ 2 ] = (-t * Math.cos(phi - Math.PI / 3)); + num = 3; + } else { /* one real solution */ + final double sqrt_D = Math.sqrt(D); + final double u = Math.cbrt(sqrt_D - q); + final double v = - Math.cbrt(sqrt_D + q); + + res[ 0 ] = u + v; + num = 1; + } + + /* resubstitute */ + + final double sub = 1.0/3 * A; + + for (int i = 0; i < num; ++i) + res[ i ] -= sub; + + if (num == 3) { fixRoots(res, eqn); - } else { - boolean neg = (R < 0.0); - double S = Math.sqrt(R2 - Q3); - if (neg) { - R = -R; - } - double A = Math.pow(R + S, 1.0 / 3.0); - if (!neg) { - A = -A; - } - double B = (A == 0.0) ? 0.0 : (Q / A); - res[roots++] = (A + B) - a; } - return roots; + + return num; + } + + private static boolean iszero(double x) { + return within(x, 0, 1e-9); + } + + private static boolean within(final double x, final double y, final double err) { + final double d = y - x; + return (d <= err && d >= -err); } /*