Приближённое решение уравнения F(x)=0 методом бисекций

/ Просмотров: 3467

В этой серии статей я постараюсь перевести на C# код, представленный в книге "Лабораторный практикум по высшей математике", которую я однажды рекомендовал обществу. Если у меня хватит терпения, после окончания работ должна получиться полная математическая библиотека.

Итак, в чём суть метода бисекций? Я постараюсь, насколько это возможно, избежать сухих формул, приведённых в книге, и описать это простым языком. Допустим, у нас есть уравнение F(x) = 0, причём известен отрезок [a,b] такой, что f(a)*f(b)<0, то есть, на всём отрезке функция непрерывно возрастает или убывает. Тогда можно точно сказать, что корень находится на этом отрезке и его можно найти методом деления пополам.

Метод бисекций - простой и надёжный метод поиска простого корня уравнения f(x)=0. Он сходится для любых непрерывных функций f(x), В том числе недифференцируемых. Для получения каждых трёх верных десятичных знаков необходимо совершить около 10 итераций.

Если на отрезке [a,b] находится несколько корней уравнения, то процесс сходится к одному из них. Метод неприменим для отыскания кратных корней четного порядка. В случае кратных корней нечётного порядка он менее точен.

Перейдём к кодированию. В этом проекте я намерен использовать всю мощь объектно-ориентированного программирования, поэтому сразу будем определять интерфейсы, которые нам понадобятся. Прежде всего, нам понадобится интерфейс IEquation, который будет содержать функцию, вычисляющую значение уравнения.

public interface IEquation
{
    double F(double x);
}

Сегодня нам понадобится только этот интерфейс. Теперь создадим класс Bisect, который будет решать уравнение методом бисекций.

public class Bisect
{
    public IEquation Equation { get; set; }
    public double Left;
    public double Right;
    public double Epsilon;

    private double Calc(Func<double,double> F)
    {
        double s = 1;
        double an = Left;      //Текущие значения
        double bn = Right;      //концов отрезка, содержащего корень
        double r = F(Left);         //Вычисление значения выражения
        double x0;
        double y;
        while (true)
        {
            x0 = 0.5 * (an + bn);       //Деление отрезка пополам
            y = F(x0);           //Значение f(x) в середине
            if ((y == 0) || ((bn - an) <= 2 * Epsilon)) break;
            s = sign(s, y) * sign(s, r);        //Здесь y = f(x0), r=f(an)
            if (s == 0)
            {
                break;
            }
            else if (s < 0)
            {
                bn = x0;
            }
            else if (s > 0)
            {
                an = x0;
                r = y;
            }
        }
        return x0;
    }

    public double Resolve(Func<double, double> F)
    {
        return Calc(F);
    }

    public double Resolve(double a, double b, double epsilon)
    {
        Left = a;
        Right = b;
        Epsilon = epsilon;
        return Calc(Equation.F);
    }

    private double sign(double x, double y)
    {
        return Math.Abs(x) * Math.Sign(y);
    }
}

Как видите, основные вычисления производятся в приватном методе Calc, который принимает в качестве параметра функцию, вычисляющую значение нашего уравнения в заданной точке. Класс содержит такие поля как левая граница отрезка, правая граница отрезка, требуемая точность и экземпляр класса, реализующего интерфейс IEquation. Фукция sign переведена из фортрана: она передаёт знак второго параметра первому параметру. Решение уравнения осуществляется вызовом одной из двух перегрузок функции Resolve: в одной из них нужно передать отрезок и точность, а уравнение задать с помощью реализации интерфейса IEquation, а во второй мы только передаём функцию, а отрезок и точность задаём присваиванием значения полям класса.

Продемонстрируем работу программы с помощью консольного приложения.

class Equat : IEquation
{
    public double F(double x)
    {
        return x * x - 5 * Math.Sin(x);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Решение уравнения f(x) = 0");
        Bisect b = new Bisect();
        Equat eq = new Equat();
        b.Equation = eq;
        Console.WriteLine("{0}", b.Resolve(1.57, 3.14, 5e-3));
        Bisect b1 = new Bisect();
        b1.Left = 0;
        b1.Right = 2;
        b1.Epsilon = 5e-5;
        Console.WriteLine("{0}", b1.Resolve(x => { return x * x - 1; }));
        Console.ReadKey();
    }
}    

Сначала здесь реализован интерфейс IEquation, с помощью которого описано уравнение, которое мы собираемся решать. После этого наше уравнение решается вызовом первой перегрузки метода Resolve, а потом другое уравнение решается с помощью другой перегрузки.

Возможно, при переводе других численных методов я буду вносить небольшие изменения в этот код, но основная часть, где производятся вычисления, останется неизменной.

Скорее всего, работоспособность других численных методов я буду проверять не в консольном приложении, а с помощью модульного тестирования. Поэтому, если не знаете, что это такое, советую познакомиться.

Исходный текст

Оставьте комментарий!

Комментарий будет опубликован после проверки

Вы можете войти под своим логином или зарегистрироваться на сайте.

(обязательно)