[C] Obliczanie sinh za pomocą szeregu

0

Zrobiłem program do obliczania sinh za pomocą szeregu z określoną dokładnością i wyliczający błąd względny ze wzoru (wartość obliczona z szeregu - wartość obliczona za pomocą sinh)/wartość obliczona za pomocą sinh:

#include <stdio.h> /*printf, scanf, fgets, putchar*/
#include <math.h> /*sinh*/
int main(void)
{
    printf("\nProgram oblicza z zadaną dokładnością wartość sinh i wartość błędu względnego w stosunku do wartości uzyskanej przy pomocy standartowej funkcji języka C");
    while(1) /*pętla nieskończona*/
    {
        double y = 0, /*wynik funkcji obliczonej za pomocą szeregu*/
               error, /*stokrotność błędu względnego*/
               x, /*argument funkcji*/
               z; /*wynik funkcji obliczonej za pomocą funkcji bibliotecznej*/
        unsigned int n, /*dokładność*/
                     k; /*zmienna oznaczająca dany element szeregu*/
        char other[2]; /*znak po cyfrze*/
        printf("\n\nPodaj wartość, dla której ma być obliczony sinh (inny znak niż cyfra i \".\" zamyka program): "); /*napisz co trzeba podać*/
        if (scanf("%lf",&x) == 0) /*pobierz liczbę, a jeśli jej nie ma, to zamknij program*/
        {
            putchar('\n'); /*pozostaw pusty wiersz po programie*/
            return 0;
        }
        fgets(other, 2, stdin); /*pobierz znak po cyfrze*/
        if (other[0] != '\n') /*jeśli to nie jest znak nowego wiersza, zamknij program*/
        {
            putchar('\n'); /*pozostaw pusty wiersz po programie*/
            return 0;
        }
        printf("Podaj dokładność (0 i inny znak niż cyfra zamyka program): "); /*napisz co trzeba podać*/
        if (scanf("%u",&n) == 0 || n == 0) /*pobierz liczbę, a jeśli jej nie ma lub jest nią 0, to zamknij program*/
        {
            putchar('\n'); /*pozostaw pusty wiersz po programie*/
            return 0;
        }
        fgets(other, 2, stdin); /*pobierz znak po cyfrze*/
        if (other[0] != '\n') /*jeśli to nie jest znak nowego wiersza, zamknij program*/
        {
            putchar('\n'); /*pozostaw pusty wiersz po programie*/
            return 0;
        }
        for (k=1; k<=n; k++) /*obliczanie szeregu*/
        {
            unsigned long a; /*zmienna do przechowywania wykładnika potęgi,
                              a potem będąca mianownikiem w elemencie szeregu*/
            unsigned int c; /*pomocnicza zmienna do pętli,
                              najpierw oznaczająca dany stopień przy liczeniu potęgi,
                              a potem liczbę o 1 mniejszą przy liczeniu silni*/
            double b; /*licznik w elemencie szeregu*/
            a = 2*k-1; /*zapamiętanie wykładnika potęgi w danym elemencie szeregu*/
            b = x; /*przypisanie do licznika argumentu funkcji*/
            for(c=1; c<a; c++) /*liczenie potęgi,
                                 wzrastanie zmiennej pomocniczej do wykładnika potęgi*/
            {
                b = b * x; /*mnożenie licznika przez argument funkcji*/
            }
            for(--c; c>=2; c--) /*zamiana wykładnika potęgi na jego silnie
                                  wykonywanie aż zmienna pomocnicza zmniejszy się do 1*/
            {
                a = a * c; /*mnożenie przez liczbę o 1 mniejszą*/
            }
            y = y + (b/a); /*dodaj element szeregu do wartości funkcji*/
        }
        printf("sinh(%.16f) = %.16f\n", x, y); /*wyświetl sinh obliczony za pomocą szeregu*/
        z = sinh(x); /*oblicz wartość sinh za pomocą funkcji bibliotecznej*/
        error = ((y - z)/z)*100; /*oblicz stokrotność błędu względnego*/
        printf("Błąd względny: %.16f", error); /*wyświetl błąd względny*/
        putchar('%'); /*pokaż znak procent*/
    }
} 

Działa bez problemu mniej więcej dla dokładności n<17 oraz argumentu -60<x<-66. Dla większych argumentów zwraca -100% jako błąd względny, dla mniejszych -NaN, a dla większych dokładności Infinity jako wartość funkcji obliczona z szeregu i jako błąd względny. Dlaczego? Program dobrze liczy potęgę i silnie. Czy -100% i -NaN oznacza liczbę mniejszą od -99,9999999999999999 a większą od -100. Czy ktoś umiałby dokładnie oszacować liczby, dla których program poprawnie liczy?

Kompiluje w systemach OpenSolaris 2008.11 i Solaris 9 za pomocą gcc -ansi -pedantic -Wall -lm. GCC nie pozwolił mi użyć "%lf" w funkcji "printf". Czy dobrze, że użyłem "%.16"? Wyliczyłem te 16 wpisując do double liczby, a następnie wyświetlając jest licząc ile cyfr po przecinku zostało poprawnie wyświetlonych.

0

Nie chce mi sie tego analizowac, więc tylko wspomnę że
17! =355687428096000 czyli ma 15 cyfr, a to dość sporo ;)

0

ale przekombinowałeś (a z komentarzami przedobrzyłeś - chociaż chwali się dbanie o nie):
A to jest takie proste:

double sinh( double x, double prec)
{
     double result = 0;
     double a,x2;
     int i = 1;
     
     a = x; // pierwszy wyraraz ciągu
     x2 = x*x; // kwadrat argumentu

     while (1)
     {
           result +=a;
           if( a < prec )
                break;
           a *= x2 / (++i * ++i); // pomnóż przez iloraz pomiędzy kolejnymi elementami sumy
           // rozpisując powyższą linijkę mamy: ++i; a *= x2 / (i * (i+1)); ++i;
     }

     return result;
}
0

++i * ++i
Jakaż piękna pułapka kryje się pod tym. Nie jest jasno określone w jakiej kolejności zostaną wykonane inkrementacje. Na różnych kompilatorach, przy różnych optymalizacjach będzie to inna kolejność inkrementacji/ewaluacji. Może się zdarzyć tak, że najpierw zostaną wykonane obydwie inkrementacje, a dopiero później zostanie pobrana wartość zmiennej i.

Trzeba użyć postaci rozpisanej. Optymalizacja wywali nadmiar instrukcji.

0

Fakt bezpieczniej rozdzielić to na dwie linijki. Poza tym można wywalić tego break-a dodając jedną klinikę za pętlą (ładniej wygląda).

double sinh( double x, double prec)
{
     double result = 0;
     double a,x2;
     int i = 1;
     
     a = x; // pierwszy wyraraz ciągu
     x2 = x*x; // kwadrat argumentu

     while (a >= prec)
     {
           result +=a;
           ++i;
           a *= x2 / (i * ++i); // pomnóż przez iloraz pomiędzy kolejnymi elementami sumy
           // rozpisując powyższą linijkę mamy: a *= x2 / (i * (i+1)); ++i;
     }
     result +=a;

     return result;
}
0

Tak też jest źle. Kolejność brania argumentów i ich ewaluacji nie jest określona. Nie ma różnicy czy napiszesz

i * ++i

czy++i * i

Obydwie instrukcje mogą być wykonane od prawej do lewej jak i od lewej do prawej. Zależy od widzi-mi-się kompilatora.
0

[adf88 juz wspomnial o kolejnosci ewaluacji wczesniej, ale chyba nikt nie zwrocil uwagi.. ale IMHO, jak nie wiedza o co chodzi, to z w/w posta nie wypatrza wiecej niz z poprzedniego..]

zgodnie z priorytetem w obu podanych przez adf88 wyrazeniach, ++i wykona sie zawsze pierwsze.. pre-increment (group3) ma prio ponad mnozeniem (group5)..

co wiecej, patrzac na wyrazenie int x = ++i * ++i; -- moze ono wykonac sie poprawnie. obydwa ++i wykonaja sie przed mnozeniem, istotnie - nie wiemy ktore z nich wykona sie pierwsze, natomiast dzieki przemiennosci mnozenia, mozna niby to sobie olac, bo przeciez dla poczatkowego i=6, czy wyjdzie 87 czy 78 to w efekcie to samo..

niestety, kompilator ma prawo zrobic to jeszcze inaczej.. zamiast:

int tmp = ++i;
int tmp2 = ++i;
int x = tmp * tmp2;

moze zrobic:

++i;
++i;
int x = i * i;

co da przy poczatkowym 6 -- 8*8

z tego samego powodu, w przypadku ++ii oraz i++i moze sie okazac, ze nie dostaniemy 76 vs. 67 tylko .. 7*7

nie wynika to z kolejnosci lewa-prawa vs. prawa-lewa, bo tutaj operatory maja rozne prio.
blad wynika z proby podwojnego czytania modyfikowanej wartosci w jednej linijce! [pierwsze czytanie: operaotr++ czyta stara wartosc, modyfikacja=++, drugie czytanie * czyta i -- ale stare czy nowe..?? ]

btw. co do lewa->prawa vs prawa-> lewa, t oo ile dobrze pamietam, taki na przyklad G++ oraz Visual zrobia to dokladnie odwrotnie, zwlaszcza jesli chodzi o kolejnosc przetwarzania parametrow funkcji ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1