0

I'm trying to find real roots for a cubic equation defined by a set of four coefficients by using Cardano method as described here. The problem is, the roots found by my implementation do not actually work - testing by inserting them in the equation give a significant error (more than the required 10^-6). Is the algorithm implemented wrong, or the error is caused by something else, like rounding accuracy?

static double CubicRoot(double n)
    {
        return Math.Pow(Math.Abs(n), 1d / 3d) * Math.Sign(n);
    }


public static List<double> SolveCubic(double A, double B = 0, double C = 0, double D = 0)
    {
        List<double> output = new List<double>();
        if (A != 0)
        {

                double A1 = B / A;
                double A2 = C / A;
                double A3 = D / A;
                double P = -((A1 * A1) / 3) + A2;
                double Q = ((2.0 * A1 * A1 * A1) / 27.0) - ((A1 * A2) / 3.0) + A3;
                double cubeDiscr = Q * Q / 4.0 + P * P * P / 27.0;
                if (cubeDiscr > 0)
                {
                    double u = CubicRoot(-Q / 2.0 + Math.Sqrt(cubeDiscr));
                    double v = CubicRoot(-Q / 2.0 - Math.Sqrt(cubeDiscr));
                    output.Add(u + v - (A1 / 3.0));
                    return output;
                }
                else if (cubeDiscr == 0)
                {
                    double u = CubicRoot(-Q / 2.0);
                    output.Add(2u - (A1 / 3.0));
                    output.Add(-u - (A1 / 3.0));
                }
                else if (cubeDiscr < 0)
                {
                    double r = CubicRoot(Math.Sqrt(-(P * P * P / 27.0)));
                    double alpha = Math.Atan(Math.Sqrt(-cubeDiscr) / (-Q / 2.0));
                    output.Add(r * (Math.Cos(alpha / 3.0) + Math.Cos((6 * Math.PI - alpha) / 3.0)) - A1 / 3.0);
                    output.Add(r * (Math.Cos((2 * Math.PI + alpha) / 3.0) + Math.Cos((4 * Math.PI - alpha) / 3.0)) - A1 / 3.0);
                    output.Add(r * (Math.Cos((4 * Math.PI + alpha) / 3.0) + Math.Cos((2 * Math.PI - alpha) / 3.0)) - A1 / 3.0);
                }
        }
        return output;
    }
Danila Smirnov
  • 138
  • 1
  • 6
  • 3
    `2u` look suspicious. You know [what it does](https://stackoverflow.com/a/3271831/1997232), right? – Sinatr Apr 05 '18 at 12:33

1 Answers1

2

A few things

  • Math.Sign will return zero on zero, which happens to be what you want in this case, but perhaps you are not so lucky with code or algorithm change.
  • You will have rounding issues and not execute cubeDiscr == 0 branch when you should. You may have rounding issues and execute the wrong > 0 and < 0 branch for the same reason. Test within a delta of zero instead (see below).
  • But the cubeDiscr == 0 branch is wrong because 1) you didn't calculate v and 2) 2u is an UInt32 with a value of 2, not 2*u.
  • Calculating alpha is wrong (see below)
  • (there may be more, but that's all I saw at a quick glance)

On calculating alpha:

double alpha = Math.Atan(Math.Sqrt(-cubeDiscr) / (-Q / 2.0));

is not the same as

double alpha = Math.Atan(Math.Sqrt(-d) / q * 2.0);
if (q > 0)                         // if q > 0 the angle becomes  PI + alpha
    alpha = Math.PI + alpha;

What's wrong with using the code included from that page?

public double Xroot(double a, double x)
{
    double i = 1;
    if (a < 0)
        i = -1;
    return (i * Math.Exp( Math.Log(a*i)/x));
}

public int Calc_Cardano()  // solve cubic equation according to cardano
{
    double p, q, u, v;
    double r, alpha;
    int res;
    res = 0;
    if (a1 != 0)
    {
        a = b / a1;
        b = c / a1;
        c = d / a1;

        p = -(a * a / 3.0) + b;
        q = (2.0 / 27.0 * a * a * a) - (a * b / 3.0) + c;
        d = q * q / 4.0 + p * p * p / 27.0;
        if (Math.Abs(d) < Math.Pow(10.0, -11.0))
            d = 0;
        // 3 cases D > 0, D == 0 and D < 0
        if (d > 1e-20)
        {
            u = Xroot(-q / 2.0 + Math.Sqrt(d), 3.0);
            v = Xroot(-q / 2.0 - Math.Sqrt(d), 3.0);
            x1.real = u + v - a / 3.0;
            x2.real = -(u + v) / 2.0 - a / 3.0;
            x2.imag = Math.Sqrt(3.0) / 2.0 * (u - v);
            x3.real = x2.real;
            x3.imag = -x2.imag;
            res = 1;
        }
        if (Math.Abs(d) <= 1e-20)
        {
            u = Xroot(-q / 2.0, 3.0);
            v = Xroot(-q / 2.0, 3.0);
            x1.real = u + v - a / 3.0;
            x2.real = -(u + v) / 2.0 - a / 3.0;
            res = 2;
        }
        if (d < -1e-20)
        {
            r = Math.Sqrt(-p * p * p / 27.0);
            alpha = Math.Atan(Math.Sqrt(-d) / q * 2.0);
            if (q > 0)                         // if q > 0 the angle becomes  PI + alpha
                alpha = Math.PI + alpha;

            x1.real = Xroot(r, 3.0) * (Math.Cos((6.0 * Math.PI - alpha) / 3.0) + Math.Cos(alpha / 3.0)) - a / 3.0;
            x2.real = Xroot(r, 3.0) * (Math.Cos((2.0 * Math.PI + alpha) / 3.0) + Math.Cos((4.0 * Math.PI - alpha) / 3.0)) - a / 3.0;
            x3.real = Xroot(r, 3.0) * (Math.Cos((4.0 * Math.PI + alpha) / 3.0) + Math.Cos((2.0 * Math.PI - alpha) / 3.0)) - a / 3.0;
            res = 3;
        }
    }
    else
        res = 0;
    return res;
 }
BurnsBA
  • 4,347
  • 27
  • 39