Помощь - Поиск - Пользователи - Календарь
Полная версия: Вычислить сумму ряда с заданной точностью с помощью рекурсии
Форум «Всё о Паскале» > Pascal, Object Pascal > Задачи
dog
Нажмите для просмотра прикрепленного файла

Вычислить сумму ряда с заданной точностью e(epsilon)>0

Есть решение, нет уверенности, что правильно:


PROGRAM PRP6;
var e, res, a, b : REAL;

FUNCTION comp(x, y, e: REAL):real;{функция нахождения суммы}
var w, z, sum: real;
BEGIN
w:=x;
z:=y;
sum:=1/(w*z);
IF 1/(w*z) >= e
then
BEGIN
sum:=comp(x, y, e);
w:=w+4;
z:=z+4;
END
else comp:=sum;

END;

BEGIN
WRITELN('Введите точность e');
READLN(e);
a:=3;
b:=5;
res:=comp(a, b, e);
WRITELN('Сумма ряда', res:5:2);
END.



Просьба проверить и поправить, если что неправильно
TarasBer
Так ты сначала у себя запусти, проверь. Ты ж не проверял.
Если 1/(w*z) >= e, то, во-первых, делается рекурсивный вызов с теми же параметрами (что приведёт к переполнению стека - рекурсии неоткуда выйти), а во-вторых, в результат ничего не записывается.
И, в третьих, с математической точки зрения то, что очередной член ряда стал меньше епсилон, ещё отнюдь не означает, что мы просуммировали ряд с достаточной точностью (я так могу гармонический ряд "просуммировать", ага). Вообще принято сначала рассчитать оценку погрешности через очередной член ряда (для каждого ряда оценка своя), а потом через эту оценку и определять, достигли мы точности, или нет.
dog
Она при некоторых значениях епсилон выдает всегда один и тот же ответ, а при некоторых действительно происходит переполнение стека.

А если так?

FUNCTION comp(x, y, e: REAL):real;{функция нахождения суммы}
var w, z, sum: real;
BEGIN
w:=x;
z:=y;
sum:=1/(w*z);
IF 1/(w*z) >= e
then
BEGIN
sum:=comp(w, z, e);{исправлено}
w:=w+4;
z:=z+4;
END
else comp:=sum;

END;




Ну на самом деле сложная задача, ряды да еще и рекурсия.
TarasBer
Дык проверь сначала, чего меня спрашивать. От замены двух букв на равные им ничего не изменится, очевидно.
volvo
Цитата
Ну на самом деле сложная задача, ряды да еще и рекурсия.
На самом деле рекурсия - проще, чем итерация. Если понимаешь сам принцип написания рекурсивных функций. Не веришь? Смотри:

1. Для начала определимся с тем, что наша рекурсия будет принимать, и что - возвращать... Ну, возвращать она будет Real, это очевидно. А принимать - 2 числа которые стоят в знаменателе, так? Не совсем. Зачем передавать их оба, если можно передать одно, только X, а зная X вычислять Y когда нужно?. Итак, с заголовком определились:
function f(X: integer; eps: real): real;


2. Теперь делаем то, без чего работающей рекурсивной функции не может существовать - опишем условие выхода из функции. Когда надо выходить? Правильно, если 1/(x * (x+2)) станет меньше eps... Так и запишем... Хм. А что запишем? Что должно вернуться, когда пришло время выйти из функции? Функция у нас считает сумму ряда, значит надо вернуть какое-то значение, которое эту сумму не испортит. Такое значение у нас только одно: ноль...

Вот, значит, и пишем условие выхода:
if 1/(x * (x+2)) < eps then f := 0


3. Ну хорошо, если условие выполнилось, понятно что будет. А если НЕ выполнилось - что делать? Словами описываем алгоритм: сложить значение текущего члена с суммой последующих. Так? Так... А на Паскаль перевести?
else { Не выполнилось условие }
f := (1/(x * (x+2))) + f(x + 4, eps); { сложим текущий с последующими }


Итого - собираем все вместе:
function f(X: integer; eps: real): real;
begin
if 1/(x * (x+2)) < eps then f := 0
else f := (1/(x * (x+2))) + f(x + 4, eps);
end;

Вот и все... Сложно? По-моему, нет...

НО. (опять это НО, я всегда ловлю себя на мысли, что слишком часто получаются какие-то оговорки, ничего не бывает просто и без проблем). Эта функция будет работать, но до определенного предела. Где предел, и почему функция не работает дальше (в настройках компилятора выставить все Run-time проверки !!!) - вот вопрос...

Первый, кто ответит на этот вопрос, правильно объяснит причину, и приведет способ исправления моей функции - получает +1 к рейтингу smile.gif ...
Unconnected
Наверное, проблема с максимальной точностью значения, которое может принять real?)
Client
ошибка в этом месте
if 1/(x * (x+2)) < eps then f := 0
проиходит тогда, когда знаменатель становится 33489 и х=183 (видимо операцию, значение которой больше 32767, в рекурсии нельзя выполнить).Поэтому, делаю проверку на вхождение в дипазон типа integer.
if (sqrt(maxint)<x ) then f:=0
else
if 1/(x*(x+2)) < eps then f:=0
else f:=(1/(x * (x+2))) + f(x+4,eps);


Добавлено через 14 мин.
это я так думаю smile.gif
volvo
Оба предыдущих ответа не содержат правильной идеи... Во-первых, Real-а вполне себе хватает. А во-вторых,
Цитата
видимо операцию, значение которой больше 32767, в рекурсии нельзя выполнить
- в рекурсии можно вычислять и гораздо бОльшие результаты. Рекурсия в этом смысле ничем от итерации не отличается.

Думаем дальше smile.gif
TarasBer
> else f := (1/(x * (x+2))) + f(x + 4, eps);

Тут какая-то неприятность со стеком сопроцессора должна произойти.
Надо

else begin
tmp_f := (1/(x * (x+2))) + f(x + 4, eps);
f := tmp_f;
end;

volvo
Цитата
Тут какая-то неприятность со стеком сопроцессора должна произойти.
Ничего не должно произойти, без разбиения все прекрасно вычисляется. Можно, конечно сделать то, что ты написал, но это не имеет решающего значения, можно обойтись и без этого.

Еще идеи есть? smile.gif Client, ведь ты показал скриншот, который должен был заставить тебя задуматься кое над чем... Чего ты не задумался?

P.S. А где, собственно, топикстартер? Ей не интересно? Или ждет, когда всё решат, все ошибки исправят, а потом втихую заберет, и все, добилась своего?
TarasBer
Тут про переполнение типа говорили.
Я бы написал дробь как (1/x)*(1/(x+2))
volvo
Цитата
Я бы написал дробь как

Хм... (Показать/Скрыть)
Client
x * (x+2)
ошибка именно здесь, даже без деления. ты про 1 картинку на 1 скрине? smile.gif
TarasBer
[offtop]
> Вот от тебя (с твоей тягой к оптимизации) я это ожидал услышать меньше всего.

Моя тяга к оптимизации упала в обморок при виде вычисления ряда через рекурсию и валяется без сознания, поэтому молчит.
[/offtop]

Проверил у себя. Вываливается именно с ~8 итераций.

А так - не вываливается:

tmp_f := f(x + 4, eps);
f := tmp_f + 1/(longint(x)*(x+2))

dog
Цитата(volvo @ 3.02.2010 18:52) *

P.S. А где, собственно, топикстартер? Ей не интересно? Или ждет, когда всё решат, все ошибки исправят, а потом втихую заберет, и все, добилась своего?


volvo, спасибо за простое и понятное объяснение рекурсии!

Тут тут я просто неожиданно как-то даже, что рекурсия так просто. Просто в легком шоке даже немножко от простоты рекурсии. Над "подводным камнем" думаю тоже.

dog
Цитата(TarasBer @ 3.02.2010 19:15) *

f := tmp_f + 1/(longint(x)*(x+2))


То есть как я понимаю при определенной величине происходит обыкновенное деление на ноль? Поэтому программа и вылетает? А ноль образуется в результате переполнения переменной x?

Вот замена входного параметра eps в функции f на double по моему немножко отодвинула порог вылета, но кардинально вопрос не решила. Будем думать дальше.
TarasBer
> Поэтому программа и вылетает?

Нет, у меня программа вылетала из-за переполнения стека сопроцессора.
Классический пример - рекурсивное вычисление факториала. Тут где-то на форуме есть пример с объяснением, поищите.
Client
volvo, кажется больше идей нету... тут приведение типов как в С может и помогло бы...
хотелось бы узнать, в чем кроется секрет. smile.gif
volvo
Приведение типов не нужно... Явное по крайней мере. Приведи неявно.

+ к этому - пока никто не показал окончательный код, который не будет вылетать при eps = 1E-8 независимо от настроек компилятора Турбо-Паскаля. То есть, независимо от того, выбран у меня режим эмуляции сопроцессора или режим 8087/80287... Окончательную версию в студию можно? А то выцеживаете по одной строке... Я что, одну строку привел?
Krjuger
Я думаю,что для начала надо разобраться почему вылетело RT215(Arithmetic overflow),а не RT205(Floating point overflow).
Arithmetic overflow - целочисленное переполнение - возникает при выполнении арифметической операции над целыми числами, когда результат операции выходит за границы соответствующего типа.

Как говорил Client, при 33к с копейками в знаменателе уже происходит вылет, при этом есть тип Short, который может содержать целые числа о -32,768 до 32,767.Принципи вот из-за этого и происходит переполнение,но для мен лично небольшая загадка почему именно тип short береться для выполнения операций,а не сам integer.

Это лиш мои догадки,скорее всего я не прав.
TarasBer
> пока никто не показал окончательный код, который не будет вылетать при eps = 1E-8

А там слишком много итераций уже надо. Стека не хватит.
А "код, который не будет вылетать" я показать могу, но он не будет соответствовать требованию делать через [s]жо[s/]рекурсию.
volvo
Цитата
А там слишком много итераций уже надо. Стека не хватит.
Ответ неверный... Для 1E-8 - вычисляется рекурсивно, ничего не вылетает. Если твой код, написанный через жорекурсию этого сделать не может - это чьи проблемы?

В общем, все ясно... Причину не ищем, пытаемся побороть следствие... Успехов.
Client
uses crt;
var
x1:real;
i:integer;
eps:real;

function f(x:integer):real;
begin
inc(i);
x1:=1.0/(x*(x+2.0)); //это и есть неявное приведение типов?
if x1 < eps then f:=0
else f:=x1 + f(x+4);
end;

begin
clrscr;
i:=0;
eps:=0.0000001;
writeln(f(3):0:7);
writeln(i);
readkey
end.
Получилось так. Терерь проблема со стеком. Убрал описание eps из функции в надажде что будет экономиться место (ведь с каждым вхождением для нее выделяется память в стеке?) и добавил счетчик (для тестирования smile.gif) Для 1E-8 происходит переполнение стека. Для 1E-7 работает, вычисляет за 791 итерацию smile.gif

Добавлено через 7 мин.
хм, 16кб за 1321 итерацию...
1321 раз выделяется память для самой функции (real - 6 байт) и лок. переменной x (integer - 2 байта).
1321*(6+2)=10568. А еще где ~5кб? уходят на вычисление
1.0/(x*(x+2.0))
?
Krjuger
Volvo,а разве я не высказал причину,ты бы хоть прокоментировал,верно ли, или не совсем,или же совсем не верно.
Lapp
Цитата(Client @ 15.02.2010 16:12) *
Получилось так. Терерь проблема со стеком.
Client, извини, но у тебя проблема не со стеком... no1.gif У тебя явная проблема с рекурсией. Твоя программа не будет считать правильно даже при гигабайтном стеке и с прекрасным приведением типов. В ней есть одна очень серьезная ошибка. Давай ты сначала отладишь прогу на FPC или Delphi, а потом уже будешь решать проблемы со стеком на ТР. Хорошо? smile.gif


Добавлено через 7 мин.
Цитата(Krjuger @ 7.02.2010 23:16) *
при этом есть тип Short, который может содержать целые числа о -32,768 до 32,767.Принципи вот из-за этого и происходит переполнение,но для мен лично небольшая загадка почему именно тип short береться для выполнения операций,а не сам integer.
Krjuger, ты извини, но ты так туманно выразился, что причину в твоих словах отыскать я так и не смог.
Типа short в TP нет. Есть тип shortint размером в 1 байт. А тип integer как раз и есть 2 байта, от -32К до +32К. Для того, чтоб не было вылетов по целым и по флоат, нужно правильно приводить типы. А причина останова в правильно реализованном алгоритме - это все-таки переполнение стека. И происходит оно в ТР при де... ой, чуть не проболтался! ))
Client
убрал свои модификации, оставил как сделал volvo с подсчетом итераций, и eps сделал глобальной. Вот как выглядит функция
function f(x:integer):real;
begin
inc(i);
if 1/(1.0* x * (x+2)) < eps then f := 0
else f := (1/(x * (x+2))) + f(x + 4);
end;
и обработчик кнопки
procedure TForm2.Button1Click(Sender: TObject);
var
r:real;
k:byte;
begin
eps:=1E-1;
Memo1.Clear;
for k := 1 to 10 do begin
i:=0;
r:=f(3);
Memo1.Lines.Add(inttostr(k)+ ') ' +FloatToStr®);
Memo1.Lines.Add('Кол-во итераций: ' + inttostr(i)+ ' eps= '+FloatToStr(eps));
Memo1.Lines.Add(' ');
eps:=eps/10;
end;
end;
Моя ошибка была в добалении переменной и результат был не правильным. Но хотя если заменить выражение переменной, измений быть не должно. Видимо при возвращении функции результата вместо бывшей x1 подставлялось самое последнее значение вместо текущего (т.е. на каждой итерации свое значение)? (в этой стоке)
else f:=x1 + f(x+4);

Хм, на Delphi вроде работает. Теперь паскаль smile.gif
uses crt;
var
x1:real;
i,k:integer;
eps:real;

function f(x:integer):real;

begin
inc(i);
if 1/(1.0 * x * (x+2)) < eps then f := 0
else f := (1/(1.0 * x * (x+2))) + f(x + 4);
end;

begin
clrscr;
i:=0;
eps:=1E-1;
for k:=1 to 7 do begin
i:=0;
writeln(f(3):0:7, ' ',i);
eps:=eps/10;
end;
readkey
end.
Выдает такие же значения (до 1Е-7, потом стек переполняется).
Rian
если дописать
uses crt,memory;

этот алгоритм рассчитает 1е-8
но не больше)))
volvo
Цитата
если дописать
uses crt,memory;

этот алгоритм рассчитает 1е-8
Ложь:
Нажмите для просмотра прикрепленного файла

Кроме того:
Цитата(volvo @ 7.02.2010 22:03) *

пока никто не показал окончательный код, который не будет вылетать при eps = 1E-8 независимо от настроек компилятора Турбо-Паскаля. То есть, независимо от того, выбран у меня режим эмуляции сопроцессора или режим 8087/80287...
Малейшая попытка задействовать сопроцессор приводит вот к чему:
Нажмите для просмотра прикрепленного файла

"Пилите, Шура, пилите..." (С)
Rian
Цитата(volvo @ 19.02.2010 12:12) *

Ложь:

правда
volvo
А я говорю - ложь... Вот мои настройки:
Нажмите для просмотра прикрепленного файла

Нечисто играешь, я тебе не разрешал настраивать компилятор, а ты это сделал... По умолчанию (и у меня именно такие установки) размер стека = 16384, ты его изменил, и хочешь мне доказать, что это сделалось подключением модуля Memory? А ты хотя бы его исходники-то видел, умник? Что такого изменилось, что после простого подключения модуля стек вдруг стал больше? Что происходит при подключении этого модуля, ты вообще знаешь?

Повторяю еще раз: я не хочу лазить в настройки и менять там что-то. Я хочу увидеть программу, которую я могу у себя откомпилировать (возможно даже консольным компилятором), запустить и посмотреть результат. Это понятно? А то смеяться над чужими программами-то ты умеешь, видели. А вот свое написать (да еще какое - простейшее, работа с вещественными числами) тебе, как выяснилось, не по силам?
Rian
Цитата(volvo @ 19.02.2010 12:28) *

А я говорю - ложь... Вот мои настройки:
Нажмите для просмотра прикрепленного файла

ну говорилось про процессор ...
а про память ничего wink.gif
ну а про memory smile.gif че б голову не поморочить
Client
{$S-}
uses crt;
var
x1:real;
i,k:integer;
eps:real;

function f(x:integer):real;

begin
inc(i);
if 1/(1.0 * x * (x+2)) < eps then f := 0
else f := (1/(1.0* x * (x+2))) + f(x + 4);
end;

begin
clrscr;
i:=0;
eps:=1E-1;
for k:=1 to 8 do begin
i:=0;
writeln(f(3):0:7,' ',eps, ' ',i);
eps:=eps/10;
end;
readkey
end.
Фишка в директиве {$S-} ? 1Е-8 работает с обычными настройками smile.gif
Rian

uses crt;
var
x1,s:real;
i,k:integer;
eps:real;
z:boolean;

procedure f(x:word);
begin
inc(i);

if 1/(1.0*x * (x+2)) < eps then

else begin
s :=s+1/(1.0*x * (x+2));
f(x+4);
end;
end;

begin
clrscr;
eps:=1E-1;
for k:=1 to 8 do begin
i:=0;
s:=0;
f(3);
writeln(s:0:7, ' ',i,' ',eps);
eps:=eps/10;
end;
readkey
end.
TarasBer
Костыль.


function f(X: longint; eps: real): real;
var
r, rf: real;
begin
r := 1/(longint(x+0)*(x+2))
+ 1/(longint(x+4)*(x+6))
+ 1/(longint(x+8)*(x+10))
+ 1/(longint(x+12)*(x+14));
if (r - 4*eps < 0) then r := 0
else begin
rf := f(x + 16, eps);
r := r + rf;
end;
f := r;
end;



Для стека 65536 и епсилон 1е-8 это работает, да. Для стека 16К надо ещё костылей повбивать.
Rian
так что ответ можно считать правильным?
volvo
Цитата
так что ответ можно считать правильным?
Твой что-ли? Размечтался:
Нажмите для просмотра прикрепленного файла
(хотя, в принципе, можешь считать как хочешь; программа которая вылетает с переполнением стека тобой тоже может считаться правильной - это на твое усмотрение)

Цитата
Костыль.
Вот именно, что костыль...

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

Всем привет, от темы отписался. Стандартно НЕРАБОТАЮЩИЕ ответы меня больше не интересуют...
TarasBer
> В общем, ребята, не напрягайтесь, сортируйте массивы и находите в них максимумы дальше, у вас это лучше получается...

Понты...
Какое чудо заставит программу, использующую стек размером 16К, которая делает кучу итераций в рекурсии, не проедать стек полностью? Компилятор ТП7 не умеет распознавать подобные рекурсии и разворачивать их в цикл.
Можно чисто итераций уменьшить (что я и сделал). А ещё можно руками править регистр стека, сохраняя его содержимое в отдельный файл.
Чудес не бывает, вот и всё.
Rian
Цитата(volvo @ 25.02.2010 13:44) *

Твой что-ли? Размечтался:
Всем привет, от темы отписался. Стандартно НЕРАБОТАЮЩИЕ ответы меня больше не интересуют...

блин... это кто еще не чисто играет?
чтобы прога показала переполнение, как это было у тебя на 6 знаках (251) мне пришлось уменьшить стек до 1024 (меньше не делается)

ты што на АРИФМОМЕТРЕ компилил??? lol.gif

PS. правильный ответ в студию!
Гость
-
Jova
Цитата(volvo @ 25.02.2010 13:44) *

Цитата
так что ответ можно считать правильным?

Твой что-ли? Размечтался:


А не поторопился ?
Решение Rian у меня работает. Нет переполнения стека

Нажмите для просмотра прикрепленного файла
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.