|
Чаще всего кубические уравнения решают "тригонометрическим" методом, но там надо вычислять специальные функции, которые по сути являются подпрограммами, хотя и весьма оптимизированными. Я же решил обойтись для нахождения одного корня одной подпрограммой 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. Наверх |