Программа на С неправильно вычисляет значение ряда Тейлора для больших n

Программа должна вычислять значение формулы ниже с точностью до n-ного члена и с максимальной точностью, то есть то бесконечности (наступает момент, когда дальше считать сумму бессмысленно, именно поэтому можем посчитать до бесконечности). Вот мой код:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

long double powl(long double x, unsigned long y) {
    long double res = 1;
    for (int i = 0; i < y; i++) {
        res *= x;
    }
    return res;
}

long double move(long double x, unsigned int n) {
    long double res = 1;
    while (n > 0) {
        res *= x / n;
        n -= 1;
    }
    return res;
}

void teylor_na(long double x, unsigned int n) {
    long double sum = 0;
    for (unsigned int i = 1; i <= n; i += 1) {
        sum += (powl(-1, i + 1)) * (move(x, i));
    }
    printf("non-accurate result is %Le\n", sum);
}

void teylor_a(long double x) {
    int critical = 0;
    long double sum = 0;
    double p_inf = powl(10, 100000);
    double n_inf = -powl(10, 100000);
    for (unsigned int i = 1; ((sum + ((powl(-1, i + 1)) * (move(x, i))) != p_inf) &&
        (sum + ((powl(-1, i + 1)) * (move(x, i))) != n_inf) &&
        (sum + ((powl(-1, i + 1)) * (move(x, i))) != sum)); i += 1) {
        sum += (powl(-1, i + 1)) * (move(x, i));
        critical = i;
    }
    printf("accurate result is %Le\n", sum);
    printf("you will get the same with non-accurate function if n = %d", critical);
}

void main() {
    long double x;
    unsigned int n;
    printf("Enter x: ");
    scanf("%Le", &x);
    printf("Enter n: ");
    scanf("%lu", &n);
    teylor_na(x, n);
    teylor_a(x);
}

При малых n функция хорошо считает ответ до n-го члена. Начиная примерно со 160 начинаются расхождения. Дан тест x = 88 n = 1000. Программу нужно исправить так, чтобы она правильно проходила его. Как вы видите, n довольно большое. Сначала программа вычисляла отдельно -1 в степени, x в степени и факториал n, но происходило переполнение. Поэтому пришлось вычислять (x^n)/(n!) отдельно в функции move. Но здесь появилось много деления, которое, как я предполагаю, дает большую погрешность и программа дает неправильный результат! Но как его убрать, я не знаю. Для большего понимания, вот что программа выдает, если каждую итерацию цикла teylor_na выводить сумму, то, что будет к ней прибавлено и номер итерации:

То есть, в конкретный момент суммы почему-то перестают чередоваться по знаку, а потом сумма и вовсе застывает на определенном значении. Почему так происходит я понять не смог


Ответы (1 шт):

Автор решения: Harry

Тут весь вопрос, вам нужны шашечки или вам надо ехать?

Дело в том, что этот ряд — это 1-exp(-x), так что для 88 вы получите 1-6e-39, и никакая точность double вам такой ответ просто не выдаст. Для точности где-то в 18 разрядов, которую обеспечивает double, вы можете считать до 40 и не более.

Так что если нужны "шашечки", т.е. просто сдать работу — рисуете код типа

double bruteForce(double x, unsigned int n)
{
    double term = x, sum = x;
    for(unsigned int i = 2; i <= n; i++)
        sum += term *= -x/i;
    return sum;
}

который считает ваш ряд (т.е. показываете свое умение преподу) и поясняете, почему для заданных им значений получится ерунда — из-за роста членов до примерно n = x, который "забьет" всю точность и того, что результат просто равен 1.

Если преподу надо какие-то другое шашечки, то ему надо точно пояснить, что он от вас хочет.

Если препод умный и хочет ехать, т.е. конкретный результат, то, как я же говорил, для таких чисел он в double не влезет, т.е. все, что вы можете — это дать результат в виде "1-ε, где ε = ....".

Как посчитать этот ε? С конца. Т.е. считать не сумму этих первых 1000 чисел, а начиная с 1001 и далее, пока точность не станет достаточной — для оценки этого хвоста ε. Зная точный результат суммы бесконечного ряда.

Но что-то "терзают меня смутные сомнения" (с), что это выходит за рамки обычной лабы для начинающих.

Других вариантов не вижу. Даже если собирать соседние члены в пары — все равно это не даст достаточного уменьшения членов для работы с указанными аргументами.

Впрочем, может, вы эти 88 и 1000 не из задания взяли, а сами "из головы придумали"? тогда просто не берите такие совершенно непригодные для численных расчетов через ряды значения. Универсальных методов ("серебряной пули") не существует...

→ Ссылка