#!/usr/bin/perl

use constant pi => 3.14159265358979;

# bernoulli($n) zwraca $n-ta liczbe Bernoulliego po wywolaniu
#   w kontekscie skalara lub wszystkie liczby Bernoulliego az do $n 
#   po wywolaniu w kontekscie listy.
#
sub bernoulli {
    my ($n) = shift;

    # Najprostsze przypadki.
    return 1    if $n == 0;
    return -0.5 if $n == 1;
    return 0    if $n % 2;

    my (@bernoulli) = (1, -0.5);
    my ($i, $j);

    # Zatrzymanie po zapelnieniu tablicy liczb Bernoulliego 
    # $n elementami skladowymi.
    #
    while ($#bernoulli < $n) {

        # Jesli szukany jest nieparzysty indeks, nalezy umiescic zero
        # i kontynuowac prace.
        $#bernoulli % 2 || (push(@bernoulli, 0), next);

        # W przeciwnym wypadku obliczenie kolejnej liczby Bernoulliego 
        # z uzyciem wczesniejszych wynikow.
        # Funkcja wybierz() jest zdefiniowana w rozdziale 14.
        #
        for ($i = 0, $j = 0; $i <= $#bernoulli; $i++) {
            $j += wybierz($#bernoulli + 2, $i) * $bernoulli[$i];
        }
        $j /= - ($#bernoulli + 2);
        push @bernoulli, $j;
    }

    # Zwrocenie wszystkich liczb w kontekscie listy
    # lub ostatniej liczby w kontekscie skalara.
    #
    return wantarray ? @bernoulli : $bernoulli[$#bernoulli];
}

$b = bernoulli(4);                     # kontekst skalara

@liczby_bernoulliego = bernoulli(4);   # kontekst listy

# zeta_iteratywnie($n, $wyrazy) oblicza pierwsze $wyrazy wyrazow
#   funkcji zeta Riemanna. Procedura dziala poprawnie zarowno dla
#   parzystych, jak i nieparzystych wartosci $n, ale powinna byc
#   uzywana tylko dla wartosci nieparzystych, poniewaz istnieje 
#   prostsze rozwiazanie dla drugiego przypadku.
#
sub zeta_iteratywnie {
    my ($n, $wyrazy) = @_;
    my ($i, $wynik);
    for ($i = 1; $i <= $wyrazy; $i++) {
        $wynik += 1 / ($i ** $n);
    }
    return $wynik;
}

sub zeta {
    my ($n) = shift;
    if ($n % 2) {                         # jest $n jest nieparzysty
        return zeta_iteratywnie($n, 10000);
    } else {
        return .5 * abs(bernoulli($n)) * ((2*pi) ** $n) / silnia($n);
    }
}

# wybierz($n, $k) to liczba sposobow, na jakie mozna wybrac $k elementow
# ze zbioru n-elementowego, gdzie kolejnosc wyboru nie ma znaczenia.

sub wybierz {
    my ($n, $k) = @_;
    my ($wynik, $j) = (1, 1);

    return 0 if $k > $n || $k < 0;
    $k = ($n - $k) if ($n - $k) < $k;

    while ( $j <= $k ) {
        $wynik *= $n--;
        $wynik /= $j++;
    }
    return $wynik;
}

sub silnia {
    my ($n, $wynik) = (shift, 1);

    # Liczby niecalkowite wymagaja uzycia funkcji gamma,
    # ktora zostanie przedstawiona w dalszej czesci rozdzialu.
    return undef unless $n >= 0 and $n == int($n);

    $wynik *= $n-- while $n > 1;
    return $wynik;
}

