Linux v příkazech - aritmetika

V Linuxu existuje několik vynikajících matematických nástrojů. Je to téma, o kterém se v češtině příliš nepíše. Zaměřme se proto na aritmetiku v shellu. Velký prostor budeme věnovat příkazu bc - kalkulátoru s libovolnou přesností. Dostane se i na reverzní polskou notaci a příkaz dc.

24.2.2005 06:00 | Jiří Václavík | přečteno 37516×

Aritmetika a shell

Aritmerický výraz má v Bashi dvě možnosti zápisu. $((výraz)) nebo $[výraz]. Celý takovýto zápis se vyčíslí a má stejný efekt, jako bychom napsali přímo výsledek výrazu. Zápis $((2+1)) je tedy ekvivalentní zápisu 3. K tisku hodnoty slouží příkaz echo:

$ echo $((9*4))
36
$

Je třeba mít na paměti, že takto lze používat jen celá čísla.

Můžeme používat i operátory pro porovnávání nebo logické operátory. Je-li výraz pravdivý, vrací výraz 1, v opačném případě 0.

$ echo $((1 == 1))
1
$ echo $((1 == 0))
0
$ echo $((2 >= 3))
0
$ echo $((!(2 >= 3)))
1
$ echo $(((2 >= 3) || (1 < 5) && 8 != 6))
1

Samozřejmě, že lze pracovat i s proměnnými. Ukažme si, jak to funguje.

$ i=7
$ j=$[$i*2]
$ echo $j
14
$

Existuje alternativa k předcházejícím příkazům, kterou je příkaz expr. Zabývat se jím nebudeme, protože je značně pomalejší.

Kalkulátor bc

10000 faktoriál, Ludolfovo číslo na 2000 desetinných míst získané programem o dvou jednoduchých příkazech nebo třeba odmocnina ze 2 vyjádřená tisícovkami číslic za desetinnou čárkou. To je bc - kalkulátor libovolné přesnosti. Limitem je jen rychlost procesoru.

bc jako příkaz je interpretem jazyka podobného Céčku. Není nijak zvlášť rozsáhlý a složitý. Přesto umí zajímavé věci.

Lze psát bc skripty nebo přímo zadávat kód v interaktivním režimu.

Použití

bc [volby] [soubor]

Jako soubor se uvádí jméno souboru s bc skriptem. Neuvedeme-li ho, dostaneme se do interaktivního režimu.

Přepínače

PřepínačPopis
-lMatematická knihovna, obsahující některé funkce, jako je například sinus, není automaticky zavedena. Musíme použít volbu -l.
-wbc má mimo standard POSIX ještě několik rozšíření. V případě jejich použití přepínač -w zajistí varování.
-sNedovoluje žádná rozšíření nad POSIX.
-hPřehled všech přepínačů.

Interaktivní režim

Příkazy se oddělují znakem nového řádku nebo středníkem. Samotné odentrování je prázdným příkazem.

Protože se často výsledek nevejde na jediný řádek, bc tiskne po určité délce nový řádek a před něj zpětné lomítko.

Řádek se zpracovává ihned po odentrování. Chceme-li tomu zamezit, musíme na konec řádku psát zpětné lomítko. V takovém případě bude brát interpret konec řádku za mezeru.

Komentáře

Vše mezi /* a */ je ignorováno

Proměnné

Platí o nich více méně to, co v jiných programovacích jazycích. Lze používat skalární proměnné i pole. Klíč pole se píše bezprostředně za jeho název do hranatých závorek.

$ bc -lq
/*proměnné*/
a=3
5+a
8

/*pole*/
pole[1]=6
pole[1]
6

Speciální proměnné

Pro bc je velmi důležitá obzvláště proměnná scale.

ProměnnáPopisImplicitně
scaleDefinuje počet číslic za desetinnou tečkou, které se vyčíslují. Určuje tedy přesnost.0, ale s volbou -l 20
ibaseUrčuje číselnou soustavu, ve které jsou zadávány hodnoty. Lze nastavit všechny soustavy mezi dvojkovou a šestnáctkovou.10
obaseUrčuje soustavu vystupujících hodnot.10
lastJe v ní uložena poslední vypisovaná hodnota (není standardem podle POSIX)0

Pokračujme stále v kódu:

666
666

obase=16
666
29A

obase=4
666
22122

Funkce

FunkcePopis
length (výraz)Vrací počet platných číslic
scale (výraz)Vrací počet číslic za desetinnou tečkou (počítá i nuly na konci).
sqrt (výraz)Vrací 2. odmocninu výrazu
read ()Čte ze standartního vstupu. Vrací zadané číslo v určené číselné soustavě. Není v POSIX.
print výrazTiskne na standartní výstup řetězec v uvozovkách nebo číslo. Není v POSIX. Podporuje i escape znaky \n, \f, \r, \a, \t, \\.

Matematická knihovna, zavedená volbou -l

FunkcePopis
s (hodnota_v_rad)Sinus
c (hodnota_v_rad)Kosinus
a (hodnota)Arkustangens
l (hodnota)Přirozený logaritmus
e (hodnota)Eulerovo číslo umocněné na hodnotu

Mimochodem, zkuste přečíst názvy funkcí odshora dolů. Dávají název proměnné scale (dobré pro zapamatování;-)

Podívejme se na několik příkladů:

sqrt(9)
3.00000000000000000000

sqrt(2)
1.41421356237309504880

/*je libo 1000 desetinných míst?*/
scale=1000
sqrt(2)
1.414213562373095048801688724209698078569671875376948073176679737990\
73247846210703885038753432764157273501384623091229702492483605585073\
72126441214970999358314132226659275055927557999505011527820605714701\
09559971605970274534596862014728517418640889198609552329230484308714\
32145083976260362799525140798968725339654633180882964062061525835239\
50547457502877599617298355752203375318570113543746034084988471603868\
99970699004815030544027790316454247823068492936918621580578463111596\
66871301301561856898723723528850926486124949771542183342042856860601\
46824720771435854874155657069677653720226485447015858801620758474922\
65722600208558446652145839889394437092659180031138824646815708263010\
05948587040031864803421948972782906410450726368813137398552561173220\
40245091227700226941127573627280495738108967504018369868368450725799\
36472906076299694138047565482372899718032680247442062926912485905218\
10044598421505911202494413417285314781058036033710773091828693147101\
71111683916581726889419758716582152128229518488472

scale=20
c(22/7)
-.99999920053355290326
/*jak vidíme, pokud je celou částí čísla 0, nemusí se uvádět*/

e(1)
2.71828182845904523536

Operace

bc se zde chová velmi podobně jako jiné jazyky. Operace +, -, *, /, % jsou samozřejmě k dispozici. Umocnění se provádí znakem ^. Modulus nemusí být celočíselný. Záleží na nastavení scale.

3
3

5*9
45

2^16
65536

/*celočíselný modulus*/
scale=0
10%3
1

/*neceločíselný modulus*/
scale=1
10%3
.1

scale=2
10%3
.01

Funguje inkrementace - postfixová i prefixová:

r=5
r++
5

r
6

r--
6

r
5

--r
4

r
4

++r
5

r
5

Lze používat i přiřazovací operace typu operátor=.

d=7
d+=12
d
19

Operátory pro porovnání <, >, <=, >=, ==, != vrací 1 (true) nebo 0 (false).

6>4
1

5>7
0

4==6
0

4!=6
1

Nad POSIX můžeme použít i logické operátory !, &&, ||.

4||0
1

0||0
0

!0
1

!5
0

45&&0
0

Priorita operátorů

Každý způsob vyhodnocování výrazů má obvykle tabulku, která definuje prioritu operací. V té naší platí, že čím je operátor výše, tím vyšší má prioritu.

AsociativitaOperátor
není() (nejvyšší priorita)
není++, --
neníunární -
zprava^
zleva*, /, %
zleva+, -
zprava=, +=, -=, *=, /=, ^=, %=
zleva<, >, <=, >=, ==, !=
není!
zleva&&
zleva|| (nejnižší priorita)

Pozor na nezvykle vysokou prioritu u přiřazení! Sledujme následující příklad:

p=2>8
0
p
2

Výsledkem porovnání je 0, ale do p byla přiřazena hodnota 2.

Řídící struktury

V testech je vždy uváděn výraz. Je-li vyhodnocen jako 0, cyklus končí nebo v případě podmínky se blok neprovádí.

Podmínka:

if (test){
    blok
}else{
    blok
}

Větev else je už mimo POSIX.

Cyklus while:

while (test){
    blok
}

Cyklus for:

for ([inicializace]; [test]; [inkrementace]){
    blok
}

break ukončuje cyklus while nebo for, continue přejde na další iteraci cyklu.

bc ukončíme příkazem quit.

Cykly a podmínky více není třeba rozebírat. Princip je stejný jako v jiných jazycích. Stačí zalistovat v některém ze seriálů, které na Linuxsoftu vycházejí.

Nějaké limity bc ve skutečnosti má. Zjistíme je příkazem limits. Jejich hodnotu ale můžeme sami libovolně nastavit při kompilaci.

Definice vlastních funkcí

Struktura:

define název_funkce (parametry){
    [auto proměnné]
    blok
    [return výraz]
}

Příkazem auto se definují lokální proměnné, return určuje návratovou hodnotu funkce.

bc v praxi

Teď přijde na řadu to slibované Ludolfovo číslo a 10000 faktoriál.

Vytvořme skript, který přijme ze standartního vstupu číslo a vytiskne jeho faktoriál.

Definujeme funkci f(), načteme číslo, vytiskneme jeho faktoriál a ukončíme.

#!/usr/bin/bc -lq

define f(n) {
    auto a, i
    a=1
    for(i=1; i<=n; i++)
    a *= i
    return a
}

n=read()
f(n)
quit

Teď skript spusťme a vyzkoušejme. Výstup pro faktoriál 10000 neuvádějme, protože by byl delší než tento článek:)

$ ./faktorial
100
93326215443944152681699238856266700490715968264381621468592963895217\
59999322991560894146397615651828625369792082722375825118521091686400\
0000000000000000000000
$

Výpočet Ludolfova čísla je velmi jednoduchý, pokud víme jak na to. Arkus tangens jedné je jeho jednou čtvrtinou. Můžeme tak napsat:

#!/usr/bin/bc -lq

scale=100
4*a(1)
quit

Proměnná scale určuje počet desetinných míst.

$ ./pi
3.141592653589793238462643383279502884197169399375105820974944592307\
8164062862089986280348253421170676
$

Kombinace bc a shellu

Zkusme spočítat Eulerovo číslo na 1000 desetinných míst pomocí bc funkce e(1) přímo z příkazového řádku. V bc k tomu potřebujeme příkazy scale=1000; e(1);. Ty pošleme rourou příkazu bc ke zpracování.

$ echo "scale=1000; e(1)" | bc -l

Jako bonus ještě přesměrujeme tisk do souboru, přidáme Perl a regulární výrazy, čímž odstraníme zpětná lomítka a nové řádky.

$ echo "scale=1000; e(1)" | bc -l | perl -e 'while(<>){$_=~s/[\\\n]//g;print;}' > euler

Zkusme podobným způsobem vypsat Ludolfovo číslo na 1000 desetinných míst na výstup.

$ echo "scale=1000;4*a(1)" | bc -l | perl -e 'while(<>){$_=~s/[\\\n]//g;print;}print "\n";'

Výstupy zde neuvádíme, protože už jsou příliš dlouhé, ale každý si to může vyzkoušet sám. Manipulací s hodnotou proměnné scale můžeme korigovat přesnost. Pro více než pár tisíc číslic se ale výpočet už neúměrně protahuje. Proto, pokud opravdu stojíte o přesnější pí (jakože v praxi více než 10 desetinných míst běžný člověk v životě nepoužije), hledejt e zde a Eulerovo číslo zde.

Polská reverzní notace a kalkulátor dc

dc je další kalkulátor s libovolnou přesností. Zprvu vysvětleme, co to vlastně polská reverzní (nebo-li postfixová) notace je. Jedná se o speciální zápis matematických výrazů, kdy změníme u jednotlivých operací pořadí operátorů a operandů tak, aby všechny operandy byly nalevo a operátor napravo. Klasickému zápisu 9 + 15 odpovídá zápis 9 15 + v polské reverzní notaci. Tato notace je výrazně jednodušší na zpracování (dc používá zásobník) a jednodušší syntaxi (nepotřebujeme závorky ani =, nutné jsou jen mezery), problémem však je jistá neintuitivnost (pro člověka je méně přehledná).

Na začátek upozorněme, že unární minus se v dc vyjadřuje nezvykle podtržítkem (to je ale při zápisu v polské reverzní notaci logické řešení).

Použití

dc [volby] [soubor]

Jako soubor se uvádí jméno souboru s dc skriptem.

Přepínače

PřepínačPopis
-e 'výraz'Vyhodnocení výrazu - viz dále
-hPřepínače

Syntaxe dc

Zkusme spočítat 8 * 9 v dc a vytisknout. Pro tisk je třeba použít speciální příkaz p.

$ dc
8 9 * p
72

Lze použít i zápis v parametru pomocí přepínače -e:

$ dc -e '8 9 * p'
72
$

Čísla, která zadáme se ukládají v zásobníku (stacku). Uvedeme-li operaci (v našem případě *), do zásobníku se uloží výsledek po násobení posledních dvou čísel v zásobníku (která se z něj samozřejmě odstraní). Poslední hodnotu v zásobníku vytiskneme příkazem p. Zde je seznam příkazů:

PříkazVýznam
pTiskne poslední hodnotu v zásobníku a znak nového řádku.
nTiskne poslední hodnotu v zásobníku a odstraní ji.
POdstraní poslední hodnotu v zásobníku.
fTiskne obsah zásobníku od poslední hodnoty k první.
cMaže celý zásobník
rVymění poslední 2 hodnoty v zásobníku.
dDuplikuje poslední hodnotu v zásobníku
sbufferMaže poslední hodnotu v zásobníku a zapíše ji do bufferu buffer. Buffery můžeme označovat jednotlivými znaky.
lbufferVyvolá hodnotu z bufferu a přidá ji na konec zásobníku
hodnota>bufferPřidá do bufferu hodnotu

Operace

K dispozici jsou standardní operace: +, -, *, /, % (modulus), ^ (mocnina), v (odmocnina).

Přesnost

Zkusme vynásobit 0.5 * 0.5.

$ dc
.5 .5 * p
.2

(Nulu před desetinnou tečku uvádět nemusíme, zápisy 0.5 a .5 jsou ekvivalentní.)

dc počítá jen s přesností, se kterou jsme zadali operandy. Pomůže zápis:

$ dc
.50 .50 * p
.25

Ale je i jiná a lepší možnost. Nastavit přesnost globálně.

PříkazVýznam
kPoslední hodnotu ze zásobníku odebere a určí podle ní přesnost (počet míst za desetinnou čárkou)
KVrací aktuální přesnost. Objeví se na konci zásobníku.

Nejsme omezeni jen na desítkovou soustavu. Následující příkazy umožňují soustavy od dvojkové po šestnáctkovou libovolně měnit:

PříkazVýznam
iPoslední hodnotu ze zásobníku odebere a určí podle ní soustavu pro vstup
oPoslední hodnotu ze zásobníku odebere a určí podle ní soustavu pro výstup
IVrací aktuální soustavu, ve které čte čísla. Objeví se na konci zásobníku.
OVrací aktuální soustavu, ve které tiskne čísla. Objeví se na konci zásobníku.

Další příkazy:

PříkazVýznam
ZDélku čísla (počet znaků, kterými je číslo napsáno. Desetinná tečka se nepočítá) přidá do zásobníku.
zDélku zásobníku (počet položek) přidá do zásobníku.
#Komentář.
!Provede systémový příkaz. Můžete tak volat klasické příkazy shellu přímo z dc.
qKonec

dc skripty

Když nechceme, nemusíme vůbec používat interaktivní režim. Ten se totiž nehodí pro používání v shellových skriptech. Klidně můžeme vytvořit jen soubor s posloupností příkazů.

#!/usr/bin/dc

6 8 + p

Na výstup se tiskne výsledek:

$ ./soucet
14
$

Makra

Zkusme následující zápis:

$ dc
[7p]s5
l5x
7

Do bufferu 5 byla uložena hodnota 7p v hranatých závorkách. Použijeme-li příkaz lbufferx, je to jako bychom provedli příkazy, uložené v bufferu.

Příklady

dc umí stejně jako bc odmocninu ze 2 na moc desetinných míst:

$ dc
100 k 2 v p
1.4142135623730950488016887242096980785696718753769480731766797379907\
324784621070388503875343276415727

Zkusme ji získat přímo z příkazového řádku:

$ dc -e'100k2vp'

Těžko bychom hledali shellový příkaz, počítající 100 desetinných míst odmocniny ze 2, který se vejde do 14 znaků. Využili jsme k tomu i možnost hustit znaky bez mezer. To lze s výjimkou zápisu několika samostatných čísel za sebou. Mezi nimi být bílý znak samozřejmě musí.

A odmocnina ze 2 naposled, tentokrát přes makra:

$ dc
[100 k 2 v p]sa
lax
1.4142135623730950488016887242096980785696718753769480731766797379907\
324784621070388503875343276415727

V dc můžeme volat příkazy shellu - například ls. Otázkou zůstává, k čemu to může být dobré...

$ dc
!ls
bc.htmleulerfaktorialforifludolf

Kolik je 2005 v trojkové soustavě?

$ dc
3 o 2005 p
2202021

Máme BAF v šestnáctkové soustavě, kolik je to v dvojkové?

$ dc
16 i 2 o BAF p
101110101111

Ještě jednou předveďme práci s buffery:

2 3 4 f
4
3
2

s1 f
3
2

l1 f
4
3
2

Nejprve jsme přidali tři hodnoty do zásobníku, poté jsme poslední z nich smazali a zkopírovali ji do bufferu 1 a nakonec opět vyvolali.

A nakonec zkusme zavolat příkaz a, který převádí poslední hodnotu v zásobníku na znak, jehož bude ona hodnota ASCII hodnotou:

$ dc
105 a p
i

Zdroje

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=713