2

I use this Cubic root implementation.

I have equation #1:

x³ -2 x² -5 x + 6 = 0

It gives me 3 complex roots ({real, imaginary}):

{-2, 7.4014868308343765E-17}
{1 , -2.9605947323337506E-16}
{3 , 2.9605947323337506E-16}

But in fact, the right result should be 3 non-complex roots: -2, 1, 3.

With this case, I can test by: apply 3 complex roots to the equation, it returns non-zero result (failed); apply 3 non-complex roots to the equation, it returns zero result (passed).

But there is the case where I apply both 3-complex roots and 3-non-complex roots to the equation (e.g. 47 x³ +7 x² -52 x + 0 = 0), it return non-zero (failed).

I think what causes this issue is because of this code:

/// <summary>
/// Evaluate all cubic roots of this <c>Complex</c>.
/// </summary>
public static (Complex, Complex, Complex) CubicRoots(this Complex complex)
{
    var r = Math.Pow(complex.Magnitude, 1d/3d);
    var theta = complex.Phase/3;
    const double shift = Constants.Pi2/3;
    return (Complex.FromPolarCoordinates(r, theta),
        Complex.FromPolarCoordinates(r, theta + shift),
        Complex.FromPolarCoordinates(r, theta - shift));
}

I know that floating point value can lose precision when calculating (~1E-15), but the problem is the imaginary part needs to decide weather it's zero or non-zero to tell if it's complex number or not.

I can't tell the user of my app: "hey user, if you see the imaginary part is close enough to 0, you can decide for yourself that the root's not a complex number".

Currently, I use this method to check:

const int TOLERATE = 15;
bool isRemoveImaginary = System.Math.Round(root.Imaginary, TOLERATE) == 0; //Remove imaginary if it's too close to zero

But I don't know if this method is appropriate, what if the TOLERATE = 15 is not enough. Or is it the right method to solve this problem?

So I want to ask, is there any better way to tell the root is complex or not?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
123iamking
  • 2,387
  • 4
  • 36
  • 56
  • Note that a cubic equation always has 3 roots (counting with multiplicity) and either 1 or all 3 of these roots are real. In particular, it is always guaranteed that at least one of the roots is real. – Stef May 19 '22 at 05:40
  • 4
    The sign of the quantity `D = (B*B - 4*A*A*A)/(-27*a*a)` tells you whether there are three real roots or a single real root and a pair of complex conjugate roots. – Mark Dickinson May 19 '22 at 06:59

1 Answers1

0

Thank you Mark Dickinson.

So according to Wikipedia:

delta > 0: the cubic has three distinct real roots

delta < 0: the cubic has one real root and two non-real complex conjugate roots.

The delta D = (B*B - 4*A*A*A)/(-27*a*a)

My ideal is:

  • delta > 0: remove all imaginary numbers of 3 roots.

  • delta < 0: find the real root then remove its imaginary part if any (to make sure it's real). Leave the other 2 roots untouched. Now I have 2 ideas to find the real root:

Ideal #1

In theory, the real root should have imaginary = 0, but due to floating point precision, imaginary can deviate from 0 a little (e.g. imaginary = 1E-15 instead of 0). So the idea is: the 1 real root among 3 roots should have the imaginary whose value is closest to 0.

Code:

NumComplex[] arrRoot = { x1, x2, x3 };

if (delta > 0)
{
    for (var idxRoot = 0; idxRoot < arrRoot.Length; ++idxRoot)
        arrRoot[idxRoot] = arrRoot[idxRoot].RemoveImaginary();
}
else
{
    //The root with imaginary closest to 0 should be the real root,
    //the other two should be non-real.
    var realRootIdx = 0;
    var absClosest = double.MaxValue;
    double abs;

    for (var idxRoot = 0; idxRoot < arrRoot.Length; ++idxRoot)
    {
        abs = System.Math.Abs(arrRoot[idxRoot].GetImaginary());
        if (abs < absClosest)
        {
            absClosest = abs;
            realRootIdx = idxRoot;
        }
    }

    arrRoot[realRootIdx] = arrRoot[realRootIdx].RemoveImaginary();
}

The code above can be wrong if there are 3 roots ({real, imaginary}) like this:

{7, -1E-99}
{3, 1E-15}//1E-15 caused by floating point precision, 1E-15 should be 0
{7, 1E-99}//My code will mistake this because this is closer to 0 than 1E-15.

Maybe if that case does happen in real life, I will come up with a better way to pick the real root.

Idea #2

Take a look at how the 3 roots calculated:

x1 = FromPolarCoordinates(r, theta);
x2 = FromPolarCoordinates(r, theta + shift);
x3 = FromPolarCoordinates(r, theta - shift);

3 roots have the form (know this by tests, not proven by math):

x1 = { A }
x2 = { B, C }
x3 = { B, -C }

Use math knowledge to prove which one among the 3 roots is the real one.

Trial #1: Maybe the root x1 = FromPolarCoordinates(r, theta) is always real? (failed) untrue because the following case proved that guess is wrong: -53 x³ + 6 x² + 14 x - 54 = 0 (Thank Mark Dickinson again)

I don't know if math can prove something like: while delta < 0: if B < 0 then x3 is real, else x1 is real?

So until I get better idea, I'll just use idea #1.

123iamking
  • 2,387
  • 4
  • 36
  • 56
  • In the case where there's only one real root, you can compute that root using only real arithmetic, avoiding the complex numbers entirely. See [Cardano's formula](https://en.wikipedia.org/wiki/Cubic_equation#Cardano's_formula). – Mark Dickinson May 19 '22 at 20:20
  • @MarkDickinson : Thank you. I just come up with new idea to pick the real root among 3 roots (check my edited answer). I want to ask: is my idea #2 correct? (x1 is always real) – 123iamking May 20 '22 at 05:04
  • 1
    Not sure. I'd be suspicious of the case where the input `B` to the cube root operation is negative: in that case, it doesn't look to me as though the first root is the real one. – Mark Dickinson May 20 '22 at 12:32
  • @MarkDickinson : Thank you again (I edited my answer). Sorry but I want to ask again: Can we tell which one among the 3 root is the real one? e.g., by checking the input: while `delta < 0`: if `B < 0` then `x3 is real` else `x1 is real` (of course this is wrong because I saw a case where `B<0` but `x1 is real`)?. – 123iamking May 24 '22 at 16:59