Hlavní navigace

Matematika v příkazovém řádku III - nástroj bc (2)

7. 2. 2006
Doba čtení: 9 minut

Sdílet

Ve třetím pokračování seriálu o programových nástrojích určených pro matematické výpočty prováděné zejména v příkazovém řádku dokončíme popis utility bc. Ukážeme si způsoby vytváření nových funkcí, použití podmínek i programových smyček a na závěr budou uvedeny demonstrační příklady provádějící pokročilejší operace.

Obsah

1. Zápis komentářů
2. Vytváření nových funkcí
3. Podmíněné příkazy
4. Tvorba programových smyček
5. Konverze vstupních a výstupních hodnot
6. Demonstrační příklady
7. Omezení jazyka a limity hodnot
8. Obsah dalšího pokračování tohoto seriálu

1. Zápis komentářů

Vzhledem k tomu, že se syntaxe použitá ve skriptech psaných pro nástroj bc do značné míry podobá syntaxi programovacího jazyka C a jeho následovníků (C++, Java apod.), je i zápis komentářů velmi podobný. Komentář (poznámka) začíná dvojicí znaků / a končí dvojicí znaků /. Komentáře se mohou vyskytovat prakticky na jakémkoli místě, ve kterém je dovoleno použít mezeru nebo jiný bílý znak. Příklad zápisu komentářů:

    /* Extract the One's digit from pi. */
    scale = 0;
    one_digit = work / 1; 

Vzhledem k tomu, že se program napsaný v bc často volá jako skript (tj. v Unixech se jedná o spustitelný soubor, na jehož prvním řádku je zapsána cesta k interpreteru), jsou povoleny i jednořádkové komentáře, které začínají znakem # a jsou ukončeny znakem pro konec řádku (CR, LF nebo CR+LF). Konec řádku není součástí komentáře, což se projevuje zejména při oddělování jednotlivých příkazů (ty mohou být odděleny buď středníkem nebo právě koncem řádku). Použití jednořádkových komentářů je velmi jednoduché:

    c=sqrt(a^2+b^2) # Pythagorova veta 

2. Vytváření nových funkcí

Při programování složitějších výpočtů je vhodné a užitečné jednotlivé části výpočtu (nebo často používané výrazy) rozdělit do funkcí. V bc jsou uživatelsky definované funkce samozřejmě podporovány a vzhledem k tomu, že se jedná o beztypový jazyk, je použití funkcí syntakticky i sémanticky jednodušší, než u staticky typovaných programovacích jazyků. Definice funkce začíná slovem define, za kterým následuje jméno funkce. Po jménu funkce může být v kulatých závorkách uveden seznam parametrů (pouze jejich názvů, typy se samozřejmě neuvádí). Pokud funkce žádné parametry nemá, použijí se pouze prázdné kulaté závorky. Za uzavírací kulatou závorkou může být zapsán libovolný počet bílých znaků následovaných otevírací složenou závorkou a znakem pro konec řádku. V GNU implementaci se může otevírací složená závorka nacházet i na novém řádku, v POSIXové verzi to není povoleno. Po otevírací složené závorce volitelně následuje seznam lokálních proměnných a potom libovolný počet příkazů. Příkazy jsou odděleny středníkem a/nebo znakem pro konec řádku. Celá funkce je ukončena uzavírací složenou závorkou. Zápis funkce by tedy měl vypadat následovně:

define jméno_funkce (parametry) {
    lokální_proměnné
    příkazy
} 

Lokální proměnné jsou platné pouze uvnitř těla funkce. Jejich deklarace začíná slovem auto (v dokumentaci se mimochodem mluví o automatických proměnných), za kterým následuje jméno či jména lokálních proměnných. Těmto proměnným je implicitně přidělena hodnota 0, samozřejmě za předpokladu, že není použit přiřazovací příkaz. Pokud má být lokální proměnnou pole, postačí za název této proměnné zapsat dvojici prázdných závorek [].

Funkce se v bc chovají poněkud odlišným způsobem, než je tomu u většiny kompilovaných jazyků. Největší odlišnost spočívá v tom, že funkce může být jednoduše předdefinována tím, že se uvede nová definice funkce se stejným jménem. To je možné využít například při interaktivním ladění funkcí, kdy se oprava ihned promítne do výpočtů. Další odlišnost spočívá v tom, že volaná funkce může pracovat s lokálními proměnnými funkce volající. To v kompilovaných jazycích většinou není možné, protože v době překladu není známo, ze kterých míst jsou funkce volány.

Hodnoty parametrů funkce jsou při jejím zavolání ukládány na zásobník, stejně jako lokální proměnné. Díky tomu jsou podporovány i rekurzivní funkce (v dnešní době se to může zdát podivné, ale některé interpretované i kompilované jazyky rekurzivní funkce nepodporují, protože používají odlišnou metodu pro práci s parametry a lokálními proměnnými).

Každá funkce musí po svém vykonání vracet nějakou hodnotu. K tomuto účelu se používá příkaz return, za nímž následuje výraz, jehož výsledek (po vyhodnocení výrazu) je použit jako návratová hodnota funkce. Pokud není žádný příkaz return ve funkci zavolán, je na konci těla funkce implicitně proveden příkaz return (0), čímž je zaručeno, že každá funkce vždy vrátí hodnotu. Pokud se příkaz return uvede bez výrazu, je to ekvivalentní zápisu return (0). Příklady funkcí budou uvedeny v dalších kapitolách.

3. Podmíněné příkazy

bc podporuje klasický podmíněný příkaz typu if, a to jak ve své zkrácené, tak i plné podobě, tj. s větví else. Plná podoba je však dostupná pouze v GNU verzi. Ve zkrácené podobě má příkaz if tvar:

if (výraz) příkaz 

V případě, že je hodnota vyhodnoceného výrazu výraz nenulová, provede se zadaný příkaz. Pokud je nutné místo jednoho příkazu uvést příkazů více, je nutné použít takzvaný příkazový blok, který začíná levou složenou závorkou a končí pravou složenou závorkou. V příkazovém bloku jsou jednotlivé příkazy odděleny středníkem, tj.:

if (výraz) {příkaz1; příkaz2...} 

Úplná varianta příkazu if má tvar:

if (výraz) příkaz1 else příkaz2 

Pokud je hodnota vyhodnoceného výrazu výraz nenulová, provede se příkaz příkaz1, v opačném případě se provede příkaz příkaz2. Samozřejmě i zde je možné použít příkazové bloky a rozšířit tak možnosti podmínek:

if (výraz) {příkaz1; příkaz2...} else {příkazX; příkazY; příkazZ} 

Po provedení jedné z větví příkazu if se pokračuje v běhu programu příkazem, který za if následuje.

4. Tvorba programových smyček

bc podporuje dva typy programových smyček. Prvním typem je počítaná smyčka for, druhým typem nepočítaná smyčka while. Nejprve si uvedeme syntaxi smyčky počítané, posléze syntaxi smyčky nepočítané. Syntaxe počítané smyčky typu for vychází z programovacího jazyka C:

for (výraz1; výraz2; výraz3) příkaz 

Výraz výraz1 je vyhodnocen ještě před prvním opakováním příkazů uvedených ve smyčce. V tomto výrazu je typicky nastavena hodnota řídicí proměnné smyčky. Výraz výraz2 je vyhodnocen před každým průchodem smyčky. Hodnotou tohoto výrazu se řídí počet opakování smyčky. Pokud je výraz2 vyhodnocen na nenulovou hodnotu, je smyčka (tj. příkazy v těle smyčky) provedena, pokud je naopak vyhodnocená hodnota výrazu výraz2 nulová, je řízení programu převedeno na příkaz, který se nachází za smyčkou. Výraz výraz3 je vyhodnocen po každém průchodu smyčkou. V tomto výrazu se typicky mění hodnota řídicí proměnné smyčky. V POSIXové verzi musí být uvedeny všechny tři zmíněné výrazy, GNU verze umožňuje jeden či více (i všechny tři) výrazy vynechat.

Za trojicí výrazů zapsaných v kulatých závorkách se nachází příkaz, který představuje tělo smyčky. Podobně jako u podmíněných příkazů, i zde je možné použít příkazový blok a rozšířit tak tělo smyčky o další příkazy. Počítaná smyčka se ukončuje buď při nulové hodnotě výrazu výraz2, nebo je možné použít příkaz break, který smyčku okamžitě ukončí. Kromě toho je k dispozici příkaz continue, který z libovolného místa v těle smyčky převede řízení na další iteraci, tj. na další vyhodnocení příkazu výraz2. Smyčku i celý běh skriptu je možné ukončit zadáním příkazu halt. Následují příklady jednoduchých počítaných smyček:

# tisk číslic 1..9
for (i=1; i<10; i++) {print i; print "\n";}

# tisk celočíselných mocnin hodnoty 2
for (i=1; i<=1024; i*=2) {print i; print "\n";}

# zkrácená verze předchozího příkladu bez nutnosti
# použití příkazového bloku
for (i=1; i<=1024; i*=2) print i, "\n" 

Následuje složitější příklad, ve kterém je definována nerekurzivní podoba funkce pro výpočet faktoriálu. Nejprve je zajištěno, že je smyčka volána pouze pro hodnoty větší než 0, posléze se smyčka provádí. Řízení obstarává proměnná i:

define factorial(n) {
    if (n<0) return 0;
    if (n==0) return 1;
    res=1;
    for (i=1; i<=n; i++)
        res*=i;
    return res;
} 

V předchozí funkci factorial() by bylo korektnější použít lokální proměnné, jinak by mohlo dojít k ovlivnění výpočtů tím, že se poškodí hodnoty v globálních proměnných res a i. Definice funkce factorial() se spolu se zavedením lokálních proměnných změní následovně:

define factorial(n) {
    auto res,i;
    if (n<0) return 0;
    if (n==0) return 1;
    res=1;
    for (i=1; i<=n; i++)
        res*=i;
    return res;
} 

Nepočítaná smyčka typu while má následující formu:

while (výraz) příkaz 

Tělo smyčky je opakovaně prováděno tak dlouho, dokud platí, že hodnota vyhodnoceného výrazu výraz je nenulová. Pokud se výraz vyhodnotí na nulu, je smyčka ukončena a řízení je předáno na příkaz, který je uveden ihned za smyčkou. Výše uvedenou funkci factorial() je možné upravit tak, aby místo počítané smyčky for používala smyčku while následovně:

define factorial(n) {
    auto res,i;
    if (n<0) return 0;
    if (n==0) return 1;
    res=1;
    i=1;
    while (i<=n)
        res*=i++;
    return res;
} 

Kdo nemá rád „céčkovské“ středníky na konci příkazů, může skript napsat následovně:

define factorial(n) {
    auto res,i
    if (n<0) return 0
    if (n==0) return 1
    res=1
    i=1
    while (i<=n) res*=i++
    return res
} 

5. Konverze vstupních a výstupních hodnot

bc obsahuje dvě důležité speciální proměnné, které určují, jakým způsobem budou načítány nebo tištěny numerické hodnoty. Proměnná ibase obsahuje základ vstupních číselných hodnot. Je možné používat číselné soustavy od základu 2 (binární číselná soustava) až do základu 16 (hexadecimální číselná soustava). U číselných soustav o základu větším jak deset jsou zbylé číslice nahrazeny velkými písmeny A-F. Písmena musí být velká, protože malá písmena značí názvy proměnných. Podobnou funkci má speciální proměnná obase, která určuje číselnou soustavu tištěných hodnot, tj. hodnot posílaných na standardní výstup. Na rozdíl od ibase může být číselná soustava na výstupu v rozsahu 2 až 999.

6. Demonstrační příklady

V této kapitole budou uvedeny některé složitější příklady, které ukazují použití nástroje bc. Začneme funkcí, kterou je možné použít pro nalezení všech prvočísel od dvojky do zadaného (celočíselného) limitu. V této funkci se využívají lokální proměnné, pole, do sebe vložené programové smyčky typu for a podmíněné příkazy. Nalezená prvočísla jsou uložena do pole prime[], odkud je možné tyto hodnoty použít v dalších výpočtech. V průběhu výpočtu jsou hodnoty nalezených prvočísel vypisovány na standardní výstup příkazem print:

define primes (limit) {
    auto num, p, root, i

    prime[1] = 2;
    prime[2] = 3;
    if (limit >= 2) print "prime 1 = 2\n"
    if (limit >= 3) print "prime 2 = 3\n";
    num = 2;
    scale = 0;

    for ( p=5; p <= limit; p += 2)  {
        root = sqrt(p);
        isprime = 1;
        for ( i = 1;  i < num && prime[i] <= root; i++ ) {
            if ( p % prime[i] == 0 ) {
                isprime = 0;
                break;
            }
        }
        if (isprime) {
            num += 1;
            prime [num] = p;
            print "prime ", num, " = ", p, "\n"
        }
    }
} 

Následuje funkce, která slouží k výpočtu distribuce číslic 0–9 vyskytujících se ve známé konstantě π na prvních n místech. Samotný výpočet čísla π je velmi jednoduchý, využije se totiž funkce pro výpočet arkustangenty pocházející z knihovny math. Arkustangenta se v této knihovně vypočte funkcí a(). Ze vztahu tan π/4=1 plyne, že π=4×arctan 1. Vhodnou manipulací se speciální proměnnou scale se může pomocí výše uvedeného vztahu vypočítat π na libovolný počet desetinných míst (v mnoha jiných programovacích jazycích využívajících přímo datové typy matematického koprocesoru, se musí π vypočítat složitějším způsobem). Výsledná distribuce je uložena do pole digits[] a následně v počítané programové smyčce typu for vytištěna:

define pi () {
    auto ix, pi, save_scale, work;

    save_scale = scale;
    scale += 5;
    print "\n\nCalculating PI to ",scale," digits.  Please wait . . .";
    pi = 4*a(1);
    scale -= 5;
    work = pi;

    print "\nCounting digits. . .";
    for (ix = 0; ix < 10; ix++) digits[ix] = 0;

    /* Extract the One's digit from pi. */
    scale = 0;
    one_digit = work / 1;

    for (ix = save_scale; ix > 0; ix--) {

        /* Remove the One's digit and multiply by 10. */
        scale = ix;
        work = (work - one_digit) / 1 * 10;

        /* Extract the One's digit. */
        scale = 0;
        one_digit = work / 1;

        digits[one_digit] += 1;
    }

    /* Restore the scale. */
    scale = save_scale;

    /* Report. */
    print "\n\n"
        print "PI to ", scale, " digits is:\n", pi/1, "\n\n"
        print "The frequency of the digits are:\n"
        for (ix = 0; ix < 10; ix++) {
            print "    ", ix, " - ", digits[ix], " times\n"
        }

    print "\n\n"
} 

7. Omezení jazyka a limity hodnot

Pomocí příkazu limits je možné zjistit některá omezení interpreteru a limity hodnot, které je možné ve skriptech používat. Na třicetidvoubitovém Linuxu většinou dostaneme následující hodnoty:

CS24_early

    BC_BASE_MAX     = 2147483647
    BC_DIM_MAX      = 65535
    BC_SCALE_MAX    = 2147483647
    BC_STRING_MAX   = 2147483647
    MAX Exponent    = 2147483647
    Number of vars  = 32767 

8. Obsah dalšího pokračování tohoto seriálu

V následujícím pokračování tohoto miniseriálu si popíšeme základy práce s utilitou dc (taktéž definovanou ve standardu POSIX), jejíž používání může být v mnoha případech efektivnější než zde popisovaná utilita bc. Zejména programátoři znalí programovacího jazyka Forth (viz ) nebo práce se staršími kalkulátory od HP mohou díky obrácené polské notaci dc využít prakticky do posledního bitu.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.