#!/usr/bin/perl

# pierwiastek($funkcja, $dolne_przyblizenie, $gorne_przyblizenie, $epsilon) 
# Podprocedura wykorzystuje metod Newtona do odnalezienia pierwiastka 
# funkcji $funkcja (przekazywanej jako odwolanie do kodu).
# Procedura przeszukuje przedzial pomiedzy $dolne_przyblizenie a $gorne_przyblizenie
# i zwraca wartosc pierwiastka z dokladnoscia $epsilon.
#
sub pierwiastek {
    my ($funkcja, $dolne_przyblizenie, $gorne_przyblizenie, $epsilon) = @_;
    my ($dolny, $gorny, $pierwiastek, $krok, $poprz_krok, $wartosc, $i);
    my ($dolna_wartosc, $gorna_wartosc) =
           ( &{$funkcja}( $dolne_przyblizenie), &{$funkcja}( $gorne_przyblizenie) );
    my $pochodna = pochodna($funkcja, $gorne_przyblizenie);

    use constant ITERACJE => 128;

    return undef if $dolna_wartosc > 0 && $gorna_wartosc > 0 or
                        $dolna_wartosc < 0 && $gorna_wartosc < 0;

    # Czy juz uzyskano pierwiastek?
    #
    return $dolne_przyblizenie if abs($dolna_wartosc) < $epsilon;
    return $gorne_przyblizenie if abs($gorna_wartosc) < $epsilon;

    if ($dolna_wartosc < 0) { ($dolny, $gorny) = ($dolne_przyblizenie, $gorne_przyblizenie) }
    else             { ($dolny, $gorny) = ($gorne_przyblizenie, $dolne_przyblizenie) }

    $pierwiastek  = ($dolne_przyblizenie + $gorne_przyblizenie) / 2;
    $krok  = $poprz_krok = abs($gorne_przyblizenie - $dolne_przyblizenie);
    $wartosc = &{$funkcja}( $pierwiastek );
    return $pierwiastek if abs($wartosc) < $epsilon;
    $pochodna = pochodna($funkcja, $pierwiastek);

    for ($i = 0; $i < ITERACJE; $i++) {

        # Czy mozna zastosowac metode Newtona? Jesli tak, uzyjmy jej.
        #
        if ( ( $pochodna * ($pierwiastek - $gorny) - $wartosc ) *
             ( $pochodna * ($pierwiastek - $dolny) - $wartosc ) < 0  and
            abs($wartosc * 2) <= abs($poprz_krok * $pochodna) ) {

            ($poprz_krok, $krok) = ($krok, $wartosc / $pochodna);
            return $pierwiastek if $krok == 0 and abs($wartosc) < $epsilon;
            $pierwiastek -= $krok;

        # W przeciwnym wypadku podzielenie gornego i dolnego przyblizenia na pol.
        #
        } else {
            ($poprz_krok, $krok) = ($krok, ($dolny - $gorny) / 2);
            $pierwiastek = $dolny + $krok;
            return $pierwiastek if $dolny == $pierwiastek and abs($wartosc) < $epsilon;
        }

        return $pierwiastek if abs($krok) < $epsilon and abs($wartosc) < $epsilon;

        $wartosc = &{$funkcja}( $pierwiastek );
        $pochodna = pochodna($funkcja, $pierwiastek);

        if ($wartosc < 0) { $dolny = $pierwiastek } else { $gorny = $pierwiastek }
    }
    return;   # Osiagnieto maksymalna liczbe iteracji.
}

sub przeplyw_pieniadza {
    my $x = shift;
    return unless $x > 0;
    return ($x ** 3) - (100 * log($x));
}

print pierwiastek(\&przeplyw_pieniadza, 2, 100, .001);


# pochodna($funkcja, $x, $delta) oblicza funkcje $funkcja (odwolanie
# do kodu) w punkcie $x. Parametr $delta jest uzywany jako dx; jesli go,
# nie podano, to poczatkowa wartosc $delta wynosi 1e-31 i jest zwiekszana 
# o jeden rzad wielkosci az do momentu zauwazenia znaczacej roznicy.
#
# Jesli funkcja $funkcja jest nieciagla, to obliczenia nie sa mozliwe.
#
sub pochodna {
    my ($funkcja, $x, $delta) = @_;

    # Wybranie wartosci delta, jesli nie zostala podana.
    #
    unless ($delta) {
        $delta = 1e-31;
        while ($x == $x + $delta) { $delta *= 10 }
    }

    # Obliczenie i zwrocenie przyblizenia pochodnej.
    #
    return ( &{$funkcja}( $x + $delta ) - &{$funkcja}( $x ) ) / $delta;
}
