Шрифт:
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<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<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
не попытается нарисовать разрушенный объект. На первом этапе мы получаем окно, в котором нарисованы оси и “настоящая” экспонента (синий цвет).
Как видим, значение
exp(0)
равно 1
, поэтому наш синий график “настоящей” экспоненты пересекает ось y в точке (0,1)
. Если присмотреться повнимательнее, то видно, что на самом деле мы нарисовали первое приближение (exp0(x)==0)
черным цветом поверх оси x. Кнопка Next позволяет получить аппроксимацию, содержащую один член степенного ряда. Обратите внимание на то, что мы показываем количество сленгов ряда, использованного для приближения экспоненты, как часть метки окна. Это функция
exp1(x)==1
, представляющая собой аппроксимацию экспоненты с помощью только одного члена степенного ряда. Она точно совпадает с экспонентой в точке (0,1)
, но мы можем построить более точную аппроксимацию. Используя два члена разложения
(1+x)
, получаем диагональ, пересекающую ось y в точке (0,1)
. С помощью трех членов разложения (1+x+pow(x,2)/fac(2))
можем обнаружить признаки сходимости.