Кубические уравнения

Чаще всего кубические уравнения решают "тригонометрическим" методом, но там надо вычислять специальные функции, которые по сути являются подпрограммами, хотя и весьма оптимизированными. Я же решил обойтись для нахождения одного корня одной подпрограммой cubic, описанной ниже. Затем, используя полученный корень, уравнение сводится к квадратному для поиска остальных корней.

typedef unsigned int nat;

// x^3 + a * x + b = 0

double cubic ( double a, double b )
{
    double s = 1.;
    while ( b + a > -1. )
    {
        a *= 4.;
        b *= 8.;
        s *= 0.5;
    }
    while ( b + a + a < -8. )
    {
        a *= 0.25;
        b *= 0.125;
        s *= 2.;
    }
    double x = 1.5;
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    x -= ( x * ( x * x + a ) + b ) / ( 3. * x * x + a );
    return x * s;
}

// x^3 + p * x + q = 0

nat root3s ( double p, double q, double * x )
{
    if ( q < 0 )
    {
        *x = cubic ( p, q );
    }
    else
    if ( q > 0 )
    {
        *x = -cubic ( p, -q );
    }
    else
    {
        *x = 0;
    }
    return 1 + root2 ( *x, p + (*x)*(*x), x+1 );
}

// x^3 + a * x^2 + b * x + c = 0

nat root3 ( double a, double b, double c, double * x )
{
    if ( c == 0 )
    {
        *x = 0;
    }
    else
    {
        const double a3 = a / 3.;
        const double p = b - a3 * a;
        const double q = c - ( a3 * a3 + p ) * a3;
        if ( q < 0 )
        {
            *x = cubic ( p, q );
        }
        else
        if ( q > 0 )
        {
            *x = -cubic ( p, -q );
        }
        else
        {
            *x = 0;
        }
        *x -= a3;
        const double t = *x * ( *x * 3. + a * 2. ) + b;
        if ( fabs ( t ) > 1e-3 )
        {
            *x -= ( *x * ( *x * ( *x + a ) + b ) + c ) / t;
        }
        a += *x;
        b += *x * a;
    }
    return 1 + root2 ( a, b, x+1 );
}

Здесь рассматриваются два типа уравнений: общее (root3) и "неполное" (root3s). Обе функции возвращают количество найденных вещественных корней, а сами корни помещаются в массив по указателю x. Вначале отыскивается один корень ( а он всегда существует) методом Ньютона. Перед этим параметры уравнения путём масштабирования меняются таким образом, чтобы корень лежал между 1 и 2. В этом случае экспериментально (несколько миллионов испытаний) установлено, что достаточно 9 итераций. На самом деле нужно даже меньше, но на всякий случай пусть будет 9. После того как найден один корень кубическое уравнение сводится к квадратному.

В первоначальном варианте функции root3 уточнения корня методом Ньютона не было. Но затем выяснилось, что в некоторых случаях ( например, когда а3 и х после cubic очень близки ) уточнение необходимо. Константа 1е-3 выбрана произвольно.

Исходники этих функций находятся в файле mathem.cpp.

Следующие две функции, кроме вещественных корней, вычисляют также комплексные корни:

void root3s ( double p, double q, double & x1, Complex & x2, Complex & x3 );

void root3 ( double a, double b, double c, double & x1, Complex & x2, Complex & x3 );
Они устроены аналогично предыдущим функциям.

Следующие две функции вычисляют корни уравнений с комплексными параметрами:
void root3s ( const Complex & p, const Complex & q, Complex & x1, Complex & x2, Complex & x3 );

void root3 ( const Complex & a, const Complex & b, const Complex & c, 
             Complex & x1, Complex & x2, Complex & x3 );
Они используют метод Кардано.
Исходники этих функций находятся в файле complex.cpp.
Описание типа Complex смотрите в разделе Класс Complex.

Наверх