0

I'm attempting to follow some psuedo code for solving cubic equations in Mathematics and Physics for Programmers, Chapter 3, as far as I can see I've followed it accurately, but I don't seem to be getting correct outputs.

For example: According to Wolfram Alpha 5x^3 + 4x^2 + 3x + 2 = 0 should give a root of x≈-0.72932, but I'm getting back -1.8580943294965526 from my script.

Can someone help me to work out what the hell I'm doing? I'm following the scripts to get a better understanding of maths and converting equations to code. But this is at the brink of my understanding so I'm finding it troublesome to debug. Coupled with the fact the book has no errata and many online reviews state the book has many errors, I'm struggling to see whether the issue is with my code, the books explanation or both.

The equation given in the book is:

eq

Then if the discriminant > 0 then root has value of r+s:

D1

if discriminant == 0 then there are two roots:

D2

if discriminant < 0 you can find three roots as follows:

D3

After finding t you can transform it to x by taking:

transform

package com.oacc.maths;

public class SolveCubic {

    public static double[] solveCubic(double a, double b, double c, double d) {
        double[] result;
        if (d != 1) {
            a = a / d;
            b = b / d;
            c = c / d;
        }

        double p = b / 3 - a * a / 9;
        double q = a * a * a / 27 - a * b / 6 + c / 2;
        double D = p * p * p + q * q;

        if (Double.compare(D, 0) >= 0) {
            if (Double.compare(D, 0) == 0) {
                double r = Math.cbrt(-q);
                result = new double[2];
                result[0] = 2 * r;
                result[1] = -r;
            } else {
                double r = Math.cbrt(-q + Math.sqrt(D));
                double s = Math.cbrt(-q - Math.sqrt(D));
                result = new double[1];
                result[0] = r + s;
            }
        } else {
            double ang = Math.acos(-q / Math.sqrt(-p * p * p));
            double r = 2 * Math.sqrt(-p);
            result = new double[3];
            for (int k = -1; k <= 1; k++) {
                double theta = (ang - 2 * Math.PI * k) / 3;
                result[k + 1] = r * Math.cos(theta);
            }

        }
        for (int i = 0; i < result.length; i++) {
            result[i] = result[i] - a / 3;
        }
        return result;
    }


    public static double[] solveCubic(double a, double b, double c) {
        double d = 1;
        return solveCubic(a, b, c, d);
    }

    public static void main(String args[]) {
        double[] result = solveCubic(5, 4, 3, 2);
        for (double aResult : result) {
            System.out.println(aResult);
        }
    }
}

I also found this code example from the book site(n.b. this is not the psuedo code from the book): http://www.delmarlearning.com/companions/content/1435457331/files/index.asp?isbn=1435457331

 on solveCubic(a,b,c,d)
 --! ARGUMENTS: 
a, b, c, d (all numbers). d is optional (default is 1)
 --! 
RETURNS: the list of solutions to dx^3+ax^2+bx+c=0


-- if d is defined then divide a, b and c by d 
 if not voidp(d) 
then
 if d=0 then return solveQuadratic(b,c,a)

set d to float(d)
 set a to a/d
 set b to b/d
 set 
c to c/d
 else
 set a to float(a)
 set b to float(b)

 set c to float(c)
 end if

 set p to b/3 - a*a/9

 set q to a*a*a/27 - a*b/6 + c/2
 set disc to p*p*p + q*q


 if abs(disc)<0.001 then 
 set r to cuberoot(-q)
 set ret to [2*r, -r]
 else if disc>0 then
 set r to cuberoot(-q+sqrt(disc))
 set s to cuberoot(-q-sqrt(disc))

set ret to [r+s]
 else

 set ang to acos(-q/sqrt(-p*p*p))
 set r to 2*sqrt(-p)

set ret to []
 repeat with k=-1 to 1
 set theta 
to (ang - 2*pi*k)/3
 ret.add(r*cos(theta))
 end repeat

end if
 set ret to ret-a/3 --NB: this adds the value to each 
element
 return ret
end 
OrderAndChaos
  • 3,547
  • 2
  • 30
  • 57
  • Yeah that's the bit I'm stuck on. – OrderAndChaos Apr 22 '17 at 12:28
  • use `Double.compare()` to compare your double variables against values. Don't use `==` and `>=`, use `Double.compare()`. –  Apr 22 '17 at 12:33
  • Great thanks I'll give that a go – OrderAndChaos Apr 22 '17 at 12:35
  • Thanks for the tip @RafaelOsipov I'll definitely use that from now on, still the same result in this case. – OrderAndChaos Apr 22 '17 at 12:41
  • The equation said to multiply by (-2πk) but then your code did a subtraction of 2πk. – Hakanai Apr 22 '17 at 13:07
  • I would never use `Double.compare(D, 0) >= 0` over `D>=0`. What are the three solutions you get? Also, maybe you should try your solution with some simpler polynomials. Eg. (x+1)(x-1)(x+2), and see if you get all three correct roots. Then maybe a case where zero is a root, and maybe a case where you have imaginary roots. – matt Apr 22 '17 at 13:11
  • @Trejkaz Yeah that's weird, the psuedo code writes `set theta to (ang--2*pi*k)/3` (not sure what the double -- is meant to be) then the code snippet I found on line goes `set theta to (ang - 2*pi*k)/3`. – OrderAndChaos Apr 22 '17 at 13:12
  • @matt I'm not sure I follow, can you explain it as though you are talking to an idiot. – OrderAndChaos Apr 22 '17 at 13:18
  • When you're debugging, it is good to have an input, and a known output. So, you've used a somewhat funny value for your example I suggest using. `x^3 +2x^2 - x -2` because I know the roots of that equation are 1, -1, -2. – matt Apr 22 '17 at 13:21
  • @RafaelOsipov - Why is that? – Oliver Charlesworth Apr 22 '17 at 13:23
  • @Rafael Osipov why would you suggest that? I agree with matt keep primitive comparisons >= <= < > Comparable::compare is for Objects – Novaterata Apr 22 '17 at 13:25
  • I think it's because of this: http://stackoverflow.com/questions/17898266/why-cant-we-use-to-compare-two-float-or-double-numbers – OrderAndChaos Apr 22 '17 at 13:29

3 Answers3

3

The error appears to be with the names of the parameters of your solveCubic method.

It seems your book is explaining how to solve the equation x3 + ax2 + bx + c = 0. You are calling your method thinking that the parameters a, b, c and d are for the equation ax3 + bx2 + cx + d = 0. However, it turns out that the body of your method is actually finding solutions to the equation dx3 + ax2 + bx + c = 0.

Aside from this naming error, the calculations appear to be correct. Try plugging your value ≈-1.858 into 2x3 + 5x2 + 4x + 3 if you don't believe me.

If you instead declare your solveCubic method as

public static double[] solveCubic(double d, double a, double b, double c) {

the parameters then correspond to the equation dx3 + ax2 + bx + c. You should then find that your method gives you the answer you expect.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
1

Okay. So, first off the equations from the book seem to be referring to this idea: If you have an equation of the form:

x^3 + ax^2 + bx + c = 0

Then by defining t as x - (a/3) you can transform this into an equation with no quadratic term, as verified by a bit of Mathematica:

Mathematica mumbling

Once you have no quadratic term, you can then apply the method given; let q be half the constant term, let p be one third the coefficient on the first power term, and define D (discriminant) as p*p*p - q*q.

All else then follows.

So why does your code not work? Because you've mislabeled the variables. a is the coefficient on x^2, not on x^3. If you call your method with the arguments (0.8, 0.6, 0.4, 1) or equivalently with (4, 3, 2, 5), you'll get the right answer.

(Or do as the other answer suggests and more around the variable names in the method declaration)

Daniel Martin
  • 23,083
  • 6
  • 50
  • 70
1

This works 100%. It has been tried and tested for all combinations.

public void solveCubicEquation(int A, int B, int C, int D) {


    double a = (double) B / A;
    double b = (double) C / A;
    double c = (double) D / A;

    System.out.println("Double values: ");
    System.out.println(a + " " + b + " " + c);
    double p = b - ((a * a) / 3.0);

    double q = (2 * Math.pow(a, 3) / 27.0) - (a * b / 3.0) + c;

    double delta = (Math.pow(q, 2) / 4) + (Math.pow(p, 3) / 27);

    if (delta > 0.001) {

        double mt1, mt2;

        double t1 = (-q / 2.0) + Math.sqrt(delta);
        double t2 = (-q / 2.0) - Math.sqrt(delta);

        if (t1 < 0) {
            mt1 = (-1) * (Math.pow(-t1, (double) 1 / 3));
        } else {
            mt1 = (Math.pow(t1, (double) 1 / 3));
        }

        if (t2 < 0) {
            mt2 = (-1) * (Math.pow(-t2, (double) 1 / 3));
        } else {
            mt2 = (Math.pow(t2, (double) 1 / 3));
        }

        x1 = mt1 + mt2 - (a / 3.0);

    } else if (delta < 0.001 && delta > -0.001) {

        if (q < 0) {

            x1 = 2 * Math.pow(-q / 2, (double) 1 / 3) - (a / 3);
            x2 = -1 * Math.pow(-q / 2, (double) 1 / 3) - (a / 3);

        } else {
            x1 = -2 * Math.pow(q / 2, (double) 1 / 3) - (a / 3);
            x2 = Math.pow(q / 2, (double) 1 / 3) - (a / 3);

        }

    } else {

        System.out.println("here");
        x1 = (2.0 / Math.sqrt(3)) * (Math.sqrt(-p) * Math.sin((1 / 3.0) * Math.asin(((3 * Math.sqrt(3) * q) / (2 * Math.pow(Math.pow(-p, (double) 1 / 2), 3)))))) - (a / 3.0);
        x2 = (-2.0 / Math.sqrt(3)) * (Math.sqrt(-p) * Math.sin((1 / 3.0) * Math.asin(((3 * Math.sqrt(3) * q) / (2 * Math.pow(Math.pow(-p, (double) 1 / 2), 3)))) + (Math.PI / 3))) - (a / 3.0);
        x3 = (2.0 / Math.sqrt(3)) * (Math.sqrt(-p) * Math.cos((1 / 3.0) * Math.asin(((3 * Math.sqrt(3) * q) / (2 * Math.pow(Math.pow(-p, (double) 1 / 2), 3)))) + (Math.PI / 6))) - (a / 3.0);

    }

}

Note: will not work for imaginary values