Ошибка с выходом за границы переменной при выислении суммы ряда
Итак, есть задачка с рядами которые нужно считать, условие задачи на скриншоте ниже. Входные данные это X - вычисляемая точка и n- количество членов ряда которые нужно вычислить(задается пользователем) Я написал программу которая работает для небольших значений, например X=3 n = 15 работает полностью корректно, но как только я повышаю X допустим до 10 и N до 30+ то начинаются проблемы. Воспользовавшись нехитрым методом дописывания printf и вывода значений после каждой итерации я выяснил что проблема заключается в том что на определенных итерациях (в зависимости от введенных значений) факториал или числитель уходит за пределы которые может уместить float. Разумеется я менял float на double, это не исправляет ситуацию а скорее чуть смещает появление inf и некорректного результата -nan(ind) в конечном итоге.
Вопрос следующий: как мне используя только Float решить эту проблему? В чем моя ошибка?
Плюс ко всему буду очень благодарен человеку который опишет все недостатки кода как со стороны оптимальности, чтобы я не допускал их в будущем, заранее огромное спасибо.
`
#include <math.h>
#include <stdio.h>
#include <locale.h>
#include <conio.h>
float GetFactorial(float n)
{
if (n == 0.0 || n == 1.0)
{
return 1.0;
}
else
{
return n * GetFactorial(n - 1.0);
}
}
float Degree(float a)
{
a = ((a * 2) + 1);
return a;
}
float Numerator(float n)
{
n = (pow(3, 2 * n)) - 1;
return n;
}
float ValueAtPoint(float p, float n)
{
float TopOfDrown=0,num=0,fact=0;
float temp = 0;
temp = Degree(n);
num = (Numerator(n) * pow(p, temp)) / (GetFactorial(n*2+1));
return num;
}
int main()
{
setlocale(LC_ALL, "Russian");
float Point = 0,NumOfRow = 0,Eps = 0,Res=0,temp = 0,sin=0;
int n = 1, i = 0,counter=1;
printf("Введите вычисляемую точку:");
Point = GetCorrect(Point);
printf("\nУкажите максимальное число элементов ряда:");
NumOfRow = GetCorrect(NumOfRow);
for (i = 1; i <= NumOfRow; i++)
{
temp = ValueAtPoint(Point, i);
if (counter % 2 == 1)
{
Res +=temp;
counter += 1;
}
else
{
Res -=temp;
counter += 1;
}
}
printf("sin^3 от %f равен:%f",Point, (3 * Res) / 4);
printf("\nПравильный ответ: %f", pow(sinf(Point),3));
return 0;
}
Ответы (3 шт):
Ну проблемы с кодом есть, конечно.
Факториал через рекурсию считать не нужно, для этого есть простой цикл. Впрочем, здесь вообще его считать в явном виде не требуется.
pow используйте там, где без него трудно обойтись, здесь он тоже нафиг не нужен
Есть проблемы с частью числителя, но остальное легко выражается через прошлый член, при этом часть больших чисел (при x>1) сможет сократиться
f = - 0.75*x
f3 = 0.0
summ = 0.0
в цикле по i
f3 = f3 * 9 + 8
f = - f * x * x * f3 / ((2*i*(2*i+1))
summ += f
Значит, так. Поскольку в задаче четко сказано, что это за функция, то...
...то первое, что мы делаем — приводим x к соответствующему значению в диапазоне от 0 до двух "пи". Есть такой прием :) И работаем.
float Series(float x, unsigned int N)
{
x = fmod(x,2*3.1415925358979323846);
float term = x*x*x;
x *= -x;
float sum = term, tr = 8;
for(int n = 2; n <= N; ++n)
{
term /= tr;
tr = (tr+1)*9-1;
term *= x*tr/(2*n*(2*n+1));
sum += term;
}
return sum;
}
Иначе вы никак не уберете бешеный рост членов до определенного момента.
Здесь проблемы будут при больших значениях N. Их можно избежать, если отказаться от N, и перейти к точности — при больших N ничего хорошего уже не будет... Или, начиная с какого-то значения N, изменить вычисление коэффициента 3 в степени 2N, и при вычислении очередного члена просто выполнять умножение на 9 (оценив, когда погрешность из-за вычитания 1 больше не будет играть роли).
Update
Реализация идеи Stanislav Volodarskiy о расписывании члена ряда как разности и с приведением к диапазону от 0 до пол-пи :)
float Series(float x, unsigned int N)
{
const float pi = 3.1415925358979323846;
int sign = x < 0 ? -1 : 1;
x = fmod(fabs(x),2*pi);
if (x > pi)
{
x -= pi;
sign = -sign;
}
if (x > pi/2) x = pi - x;
float term2 = x*x*x/8, term1 = 9*term2;
x *= -x;
float sum = term1 - term2;
for(int n = 2; n <= N; ++n)
{
float quot = 2.*n;
quot *= quot+1;
term1 *= 9*x/quot;
term2 *= x/quot;
sum += term1-term2;
}
return sign*sum;
}
Почему не надо считать во float?
Расчёт выполнен для x = 6.283185. В таблице приведены слагаемые (члены ряда) и ошибка их представления во float. Внизу итог. Обращу внимание что истинное значение функции ноль. А самый лучший метод вычислит 0.089656±0.782165:
итерация слагаемое(ошибка представления) 1 248.050186(0.000016) 2 -4896.313477(0.000246) 3 41881.378906(0.001955) 4 -206928.656250(0.007813) 5 668480.875000(0.031250) 6 -1522555.500000(0.062500) 7 2576064.750000(0.125000) 8 -3365039.500000(0.125000) 9 3495958.500000(0.125000) 10 -2957462.000000(0.125000) 11 2076686.125000(0.062500) 12 -1229764.000000(0.062500) 13 622424.750000(0.031250) 14 -272353.531250(0.015625) 15 104052.429688(0.003906) 16 -35009.871094(0.001953) 17 10453.115234(0.000488) 18 -2788.327148(0.000122) 19 668.494263(0.000031) 20 -144.829163(0.000008) 21 28.493151(0.000001) 22 -5.113019(0.000000) 23 0.840280(0.000000) 24 -0.126937(0.000000) 25 0.017687(0.000000) 26 -0.002280(0.000000) 27 0.000273(0.000000) 28 -0.000030(0.000000) 29 0.000003(0.000000) 30 -0.000000(0.000000) ------------------ 0.089656(0.782165)
#include <math.h>
#include <stdio.h>
float err(float x) {
x = fabsf(x);
float y = nextafterf(x, INFINITY);
return (y - x) / 2;
}
typedef struct {
float x;
float e;
} range_t;
range_t f(float x, int n) {
float sum_err = 0;
float term1 = -0.75f * x;
float term2 = -0.75f * x;
float xx = -x * x;
float sum = 0;
for (int i = 0; i < n; ++i) {
float f = xx / (float)((2 * i + 2) * (2 * i + 3));
term1 *= 9 * f;
term2 *= f;
printf(" %3d %15.6f(%f)\n", i + 1, term1 - term2, err(term1) + err(term2));
sum_err += err(term1) + err(term2);
sum += term1 - term2;
}
range_t r = {sum, sum_err};
return r;
}
int main() {
float x = 2*3.1415925358979323846f;
range_t r = f(x, 30);
printf("%f %f(%f) %f\n", x, r.x, r.e, powf(sinf(x), 3));
}