Литмир - Электронная Библиотека
A
A

15.5. Аппроксимация

Рассмотрим еще один небольшой пример построения графика функции: “анимируем” вычисление экспоненты. Наша цель — дать вам почувствовать математические функции, продемонстрировать применение графиков для иллюстрации вычислений, показать фрагменты кода и, в заключение, предупредить о типичных проблемах, связанных с вычислениями.

Один из способов вычисления экспоненты сводится к суммированию степенного ряда.

e<sup>x </sup>= 1 + x + x<sup>2</sup>/2! + x<sup>3</sup>/3! + x<sup>4</sup>/4! + ...

Чем больше членов ряда мы вычислим, тем точнее будет значение

e<sup>x</sup>
; иначе говоря, чем больше членов ряда мы вычисляем, тем больше правильных цифр найдем в результате. В программе мы суммируем ряд и строим график его частичных сумм. В этой формуле знак восклицания, как обычно, обозначает факториал, т.е. мы строим графики функций в следующем порядке:

exp0(x) = 0   // нет членов

exp1(x) = 1   // один член

exp2(x) = 1+x // два члена ; pow(x,1)/fac(1)==x

exp3(x) = 1+x+pow(x,2)/fac(2)

exp4(x) = 1+x+pow(x,2)/fac(2)+pow(x,3)/fac(3)

exp5(x) = 1+x+pow(x,2)/fac(2)+pow(x,3)/fac(3)+pow(x,4)/fac(4)

...

Каждая функция немного точнее приближает

e<sup>x</sup>
, чем предыдущая. Здесь
pow(x,n)
— стандартная библиотечная функция, возвращающая
x<sup>n</sup>
. В стандартной библиотеке нет функции, вычисляющей факториал, поэтому мы должны определить ее самостоятельно.

int fac(int n) // factorial(n); n!

{

  int r = 1;

  while (n&gt;1) {

    r*=n;

    ––n;

  }

  return r;

}

Альтернативная реализация функции

fac()
описана в упр. 1. Имея функцию
fac()
, можем вычислить n-й член ряда.

double term(double x, int n) { return pow(x,n)/fac(n); } // n-й

                                                         // член ряда

Имея функцию

term()
, несложно вычислить экспоненты с точностью до
n
членов.

double expe(double x, int n) // сумма n членов для x

{

  double sum = 0;

  for (int i=0; i&lt;n; ++i) sum+=term(x,i);

  return sum;

}

Как построить график этой функции? С точки зрения программиста трудность заключается в том, что наш класс

Function
получает имя функции одного аргумента, а функция
expe()
имеет два аргумента. В языке С++ нет элегантного решения этой задачи, поэтому пока воспользуемся неэлегантным решением (тем не менее, см. упр. 3). Мы можем удалить точность
n
из списка аргументов и сделать ее переменной.

int expN_number_of_terms = 10;

double expN(double x)

{

  return expe(x,expN_number_of_terms);

}

Теперь функция

expN(x)
вычисляет экспоненту с точностью, определенной значением переменной
expN_number_of_terms
. Воспользуемся этим для построения нескольких графиков. Сначала построим оси и нарисуем истинный график экспоненты, используя стандартную библиотечную функцию
exp()
, чтобы увидеть, насколько хорошо она приближается функцией
expN()
.

Function real_exp(exp,r_min,r_max,orig,200,x_scale,y_scale);

real_exp.set_color(Color::blue);

Затем выполним цикл приближений, увеличивая количество членов ряда

n
.

for (int n = 0; n&lt;50; ++n) {

  ostringstream ss;

  ss &lt;&lt; &quot; приближение exp; n==&quot; &lt;&lt; n ;

  win.set_label(ss.str());

  expN_number_of_terms = n;

  // следующее приближение:

  Function e(expN,r_min,r_max,orig,200,x_scale,y_scale);

  win.attach(e);

  win.wait_for_button();

  win.detach(e);

}

Обратите внимание на последний вызов

detach(e)
в этом цикле. Область видимости объекта
e
класса
Function
ограничена телом цикла
for
. Каждый раз, кода мы входим в этот блок, мы создаем новый объект
e
класса
Function
, а каждый раз, когда выходим из блока, объект
e
уничтожается и затем заменяется новым. Объект класса
Window
не должен помнить о старом объекте
e
, потому что он будет уничтожен. Следовательно, вызов
detach(e)
гарантирует, что объект класса
Window
не попытается нарисовать разрушенный объект.

На первом этапе мы получаем окно, в котором нарисованы оси и “настоящая” экспонента (синий цвет).

Программирование. Принципы и практика использования C++ Исправленное издание - _162.png

Как видим, значение

exp(0)
равно
1
, поэтому наш синий график “настоящей” экспоненты пересекает ось y в точке
(0,1)
. Если присмотреться повнимательнее, то видно, что на самом деле мы нарисовали первое приближение
(exp0(x)==0)
черным цветом поверх оси x. Кнопка Next позволяет получить аппроксимацию, содержащую один член степенного ряда. Обратите внимание на то, что мы показываем количество сленгов ряда, использованного для приближения экспоненты, как часть метки окна.

204
{"b":"847443","o":1}