Почему оператор зацикливания не работает, когда 10-й цикл заканчивается в программе на Си? - PullRequest
0 голосов
/ 25 сентября 2019

Я пытаюсь вычислить приближение ряда, и я устанавливаю точность как 1E-6.

int i=1;
    float x;
    scanf("%f",&x);
    double sx = x,temp;
    do{
        temp = (pow(x,((2*i)+1)))/((2*i+1)*(fact(i))); //fact() is a function counting factorial
        if((i+2)%2==1) sx-=temp;                        
        else sx+=temp;
        printf("%lf %lf\n",temp,sx); 
        i++;
    }while(temp>1E-6);
    printf("%lf",sx);

Когда вход меньше 2, я получаю ожидаемый результат. Но когда ввод приходит3, есть отрицательное число.Это из-за переполнения стека?Но факт (10) не так велик, не так ли?Как я могу получить правильный результат?

Series

Ответы [ 2 ]

1 голос
/ 25 сентября 2019

Вы спрашивали, есть ли 10!слишком велик, но если бы вы наблюдали i в точке переполнения значения, вы увидите, что i == 12.Тогда выражение для temp будет:

3 25 / 25 x 12!

Подвыражение 25 x 12!имеет значение 11975040000, для которого потребуется 34 бита, если оно является целочисленным выражением, которое будет, если fact() является целочисленной функцией.Если это 32-разрядная целочисленная функция, она будет переполнена.

Если fact() изменить на 64-разрядный тип без знака, он получит дальнейшее развитие, но это хорошо только для 19!и подвыражение делителя переполняется в i=18 (29 x 18!) перед сходимостью.

Он будет сходиться для i = 3, используя следующую реализацию факториальной функции:

double fact(int n)
{
    if (n >= 1)
        return n * fact(n - 1);
    else
        return 1;
}

С результатом 0,886207.Для произвольно больших значений x вам понадобится математическая библиотека произвольной точности или «bignum», а не встроенные типы.

Вдохновленный комментарием Ли Крокера, я пробовал различные выражения (проверено на GDB Online )

1) Исходя из моих double fact( int ) и менее ваших посторонних скобок;

double e = (2*i)+1 ;
temp = pow(x,e) / (e * fact(i));

Результаты для x = 1 до 9:

x: 1.0  s(x): 0.746824                                                                                                                          
x: 2.0  s(x): 0.882081                                                                                                                          
x: 3.0  s(x): 0.886207                                                                                                                          
x: 4.0  s(x): 0.886227                                                                                                                          
x: 5.0  s(x): 0.886227                                                                                                                          
x: 6.0  s(x): 0.889677                                                                                                                          
x: 7.0  s(x): 2453.707036                                                                                                                       
x: 8.0  s(x): 1055556860.115990                                                                                                                 
x: 9.0  s(x): -nan 

2) Использование tgamma():

temp = pow(x,e) / (e * tgamma(i+1)) ; 

Результат:

x: 1.0  s(x): 0.746824                                                                                                                          
x: 2.0  s(x): 0.882081                                                                                                                          
x: 3.0  s(x): 0.886207                                                                                                                          
x: 4.0  s(x): 0.886227                                                                                                                          
x: 5.0  s(x): 0.886226                                                                                                                          
x: 6.0  s(x): 0.882858                                                                                                                          
x: 7.0  s(x): -83.077451                                                                                                                        
x: 8.0  s(x): 3729162898.780624  

3) Использование lgamma():

temp = pow(x,e) / exp(log(e) + lgamma(i+1)) ;

Результат:

x: 1.0  s(x): 0.746824                                                                                                                          
x: 2.0  s(x): 0.882081                                                                                                                          
x: 3.0  s(x): 0.886207                                                                                                                          
x: 4.0  s(x): 0.886227                                                                                                                          
x: 5.0  s(x): 0.886236                                                                                                                          
x: 6.0  s(x): 1.904565                                                                                                                          
x: 7.0  s(x): -322346.422518                                                                                                                    
x: 8.0  s(x): 5416428382.734000                                                                                                                 
x: 9.0  s(x): -nan       

Результаты отличаются для x = 6.0, но все версии разбиты примерно там.Что лучше, я не ясно - fact() Я подозреваю.Стандартные библиотечные функции не предлагают никаких существенных улучшений.

4) Добавлено выражение Ли для проверки (если я правильно понял его ответ):

temp = exp(e * log(x) - log(e) - lgamma(i+1)) ;

Результат:

x: 1.0  s(x): 0.746824
x: 2.0  s(x): 0.882081
x: 3.0  s(x): 0.886207
x: 4.0  s(x): 0.886227
x: 5.0  s(x): 0.886245
x: 6.0  s(x): 2.892730
x: 7.0  s(x): 96596.597282
x: 8.0  s(x): -720878097610.696167
x: 9.0  s(x): -18109761159507625984.000000

Для справки мой тестовый код:

double s( double x )
{
    double sx = x ;
    double temp = 1.0 ;

    for( int i = 1; temp > 1e-6; i++ ) 
    {
        double e = (2*i)+1 ;

        // Uncomment one of the following:
        //temp = pow(x,e) / (e * fact(i));
        //temp = pow(x,e) / (e * tgamma(i+1)) ; 
        //temp = pow(x,e) / exp(log(e) + lgamma(i+1)) ;
        //temp = exp(e * log(x) - log(e) - lgamma(i+1)) ;

        sx += (i % 2) == 0 ? temp : -temp ;  
    }

    return sx ;
}

int main()
{
    for( double x = 1.0; x < 10.0; x += 1.0 )
    {
        printf( "x: %.1f  s(x): %lf\n", x, s(x) ) ;
    }

    return 0;
}
0 голосов
/ 26 сентября 2019

Экспоненты и факториалы растут очень быстро и часто переполняют целочисленные типы и вызывают проблемы с точностью, даже с типами с плавающей запятой.Математики часто делают подобные вычисления в логарифмическом пространстве.Математическая библиотека C имеет функцию lgamma (), например, которая вычисляет натуральный логарифм гамма-функции (gamma (x) = fact (x-1)) с точностью, которая может быть потеряна, если вам придется представлятьсначала весь факториал, а затем возьмите его журнал.

Итак, предполагая double x и int n, вы можете переписать pow(x,n) / (n * fact(n / 2)) как exp(n * log(x) - log(n) - lgamma(1 + n / 2)) и получить разумные результаты для больших n.

Вот мой код:

#include <stdio.h>
#include <math.h>

void series(double base) {
    double x = base;
    double sx = x, temp;
    int i = 1;

    do {
        int e = 2 * i + 1;

        temp = exp(e * log(x) - log(e) - lgamma(i+1));
        if (1 == i%2) sx -= temp;                        
        else sx += temp;

        printf("%d %lf %lf\n", i, temp, sx); 
        i += 1;
    } while (temp > 1E-6);
}

int main(void) {
    series(3.0);
}

и вывод:

1 9.000000 -6.000000
2 24.300000 18.300000
3 52.071429 -33.771429
4 91.125000 57.353571
5 134.202273 -76.848701
6 170.333654 93.484953
7 189.800357 -96.315405
8 188.404766 92.089362
9 168.572686 -76.483324
10 137.266330 60.783006
11 102.542831 -41.759826
12 70.754554 28.994728
13 45.355483 -16.360755
14 27.146262 10.785507
15 15.236934 -4.451427
16 8.051335 3.599907
17 4.018901 -0.418994
18 1.900832 1.481838
19 0.854220 0.627618
20 0.365648 0.993266
21 0.149418 0.843848
22 0.058409 0.902257
23 0.021883 0.880374
24 0.007871 0.888245
25 0.002723 0.885522
26 0.000907 0.886429
27 0.000291 0.886138
28 0.000090 0.886228
29 0.000027 0.886201
30 0.000008 0.886209
31 0.000002 0.886207
32 0.000001 0.886207
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...