2
cherep36Увы, не смог нормально разобраться с вашим кодом. Странности в нем начались уже с самого начала, прямо с ввода данных. В комментарии указано, что верхний предел должен быть больше нижнего и тут же начинается цикл с прямо противоположным условием. :) Вам нужно позволить пользователю ввести оба предела, а потом уже проверяйте их неравенством; если проверка провалилась или выдайте ошибку или попросите пользователя повторить ввод.
Дальше ещё страшнее. Нет, возможно, что существенных ошибок-то и нет, но понять, что же вы там именно делаете, не получается. :) Давайте попробуем немного упростить логику. Итак, пусть у нас есть функция и кое-какие глобальные переменные (потом вы их сможете выкинуть и написать функтор-замыкание или ещё как всё это дело обернуть в любые c++ примочки):
float a, b, A, B, C, eps;
float f(float x) {return A*x*x+B*x+C;}
Теперь напишем функцию (назовем её, скажем,
Boxes), которая будет интегрировать
f(x) вычисляя её в некотором заданном количестве регулярно расположенных точек (у
Boxes может быть всего один параметр, -- номер её вызова, -- но мы определим её вообще без параметров; причины станут ясны из нижеследующего). Здесь вам все знакомо. Разбиваем промежуток
на интервалы, вычисляем функцию в серединах этих интервалов, умножаем эти значения на длину интервала и полученные площади прямоугольников складываем. Но будет в нашей функции и кое-что необычное -- она будет устроена так, чтобы ею можно было воспользоваться многократно, постоянно увеличивая число интервалов разбиения и увеличивая таким образом точность. Грубо говоря, мы можем делать что-то вроде
while(...) Result=Boxes(). Идея в том, что мы должны определить, когда, т.е., при каком количестве интервалов разбиения точность окажется удовлетворительной. К вопросу оценки этой точности вернемся чуть позже. Увеличение количества интервалов удобно производить действительно просто деля на две части уже имевшиеся интервалы (т.е. при этом число интервалов будет расти как
, где
-- количество вызовов
Boxes).
Код может быть таким:
float Boxes()
{
float x, IntervalLength, Sum=0;
int Intervals=1;
static int InvocationNumber=1;
static float IntermediateResult=(b-a)*f((a+b)/2.0);
if(InvocationNumber++==1) return IntermediateResult;
for(int i=1; i<InvocationNumber-1; i++)
Intervals*=2; // Intervals=pow(2, n-1).
IntervalLength=(b-a)/(float)Intervals;
x=a+IntervalLength/2.0; // Now x is midpoint of the first interval.
while(x<b) // Scan [a; b].
{
Sum+=f(x);
x+=IntervalLength; // Shift to the next "control point."
}
// Arithmetic mean of old and new results.
IntermediateResult+=Sum*IntervalLength;
return IntermediateResult/=2.0;
}
Обратите внимание, в фукции мы заводим счетчик вызовов
InvocationNumber, значение которого сохраняется между вызовами (
static). Если это самый первый вызов, то в качестве промежуточного результата
IntermediateResult (который тоже сохраняется между вызовами) мы сохраняем приблизительную площадь области под графиком, считая её прямоугольников с высотой равной значению функции в точке, лежащей ровно посередине между
a и
b (длина прямоугольника равна длине единственного на данной "итерации" интервала, совпадающего со всем отрезком
). При следующих вызовах функции мы добавляем новые интервалы, подсчитываем их количество и кладем его в
Intervals. Делением длины
b-a на количество интервалов мы естественно получаем длину интервала
IntervalLength. Далее мы находим середину самого первого (левого) интервала и прыгаем на
IntervalLength пока не достигнем верхнего предела, т.е. пока не посетим середины всех интервалов. Значения функции во всех этих точках тупо суммируем.
Осталось разобрать последние две, самые сложные строчки этого кода. Их идея проста -- мы просто уточняем промежуточный результат, хранящийся в
IntermediateResult, путем подсчета среднего арифметического его старого сохраненного с предыдущей итерации значения, и нового, полученного умножением только что посчитанной суммы значений функции
f на длину интервала, т.е. суммированием площадей прямоугольников, построенных на всех посещенных интервалах (в предыдущей строчке,
IntervalLength как-бы "вынесли за скобки").
Как теперь реализовать обещанный контроль точности? Для этого будем писать ещё одну функцию
Integrate, которая при своей работе будет периодически вызывать
Boxes, проверяя под контролем
eps(ilon)'а насколько быстро будут меняться результаты, и как только изменение станет слишком медленным, функция возвратит результат:
float Integrate()
{
float Result, PrevResult;
for(int i=1; i<=20; i++) // Here, 2^19 is maximal number of intervals.
{
Result=Boxes(); // Refine result.
// Check for convergency (but perform a few,
// say 5-10, iterations without precision-check.)
if(i>5 && fabs(Result-PrevResult)<eps*fabs(PrevResult))
return Result;
PrevResult=Result;
}
std::cerr << "crash";
return 0;
}
Цикл здесь, как видите конечен, так, на всякий случай; за его пределами водятся драконы (такая вот странная аварийная проверка на сходимость). :)
Как пользоваться понятно:
std::cin >> a >> b >> A >> B >> C >> eps;
std::cout << Integrate();
P.S.: Этот разжеванный пост написан в стиле вольной интерпретации (по-памяти) похожего материала из Num.Recipes in C. В отличие от источника, я мог допустить грубые ошибки. :) Но я надеюсь, что написанное хоть как-то позволит прояснить ситуацию с обсуждаемым проблемным кодом.
P.P.S.: Мои фрагменты на тестовых данных тоже выдают число, меньшее желаемого, а именно примерно 606. Результат улучшается если дать программульке поработать немного дольше без проверок точности (то есть исправить
i>5 на, скажем,
i>10), либо осуществлять проверку по формуле, похожей на вашу, а именно если удалить
*fabs(PrevResult) из функции
Integrate (в обоих случаях ответ будет приближаться к 612).