Читаем Программирование полностью

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

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


e= 1 + x + x2/2! + x3/3! + x4/4! + ...


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


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)

...


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


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

{

  int r = 1;

  while (n>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

  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<50; ++n) {

  ostringstream ss;

  ss << " приближение exp; n==" << 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 не попытается нарисовать разрушенный объект.

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



Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже