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 37519×
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ší.
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.
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č | Popis |
-l | Matematická knihovna, obsahující některé funkce, jako je například sinus, není automaticky zavedena. Musíme použít volbu -l. |
-w | bc má mimo standard POSIX ještě několik rozšíření. V případě jejich použití přepínač -w zajistí varování. |
-s | Nedovoluje žádná rozšíření nad POSIX. |
-h | Přehled všech přepínačů. |
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.
Vše mezi /* a */ je ignorováno
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
Pro bc je velmi důležitá obzvláště proměnná scale.
Proměnná | Popis | Implicitně |
scale | Definuje počet číslic za desetinnou tečkou, které se vyčíslují. Určuje tedy přesnost. | 0, ale s volbou -l 20 |
ibase | Určuje číselnou soustavu, ve které jsou zadávány hodnoty. Lze nastavit všechny soustavy mezi dvojkovou a šestnáctkovou. | 10 |
obase | Určuje soustavu vystupujících hodnot. | 10 |
last | Je 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 | Popis |
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ýraz | Tiskne na standartní výstup řetězec v uvozovkách nebo číslo. Není v POSIX. Podporuje i escape znaky \n, \f, \r, \a, \t, \\. |
Funkce | Popis |
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
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
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.
Asociativita | Operá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.
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.
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.
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
$
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.
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í).
dc [volby] [soubor]
Jako soubor se uvádí jméno souboru s dc skriptem.
Přepínač | Popis |
-e 'výraz' | Vyhodnocení výrazu - viz dále |
-h | Přepínače |
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říkaz | Význam |
p | Tiskne poslední hodnotu v zásobníku a znak nového řádku. |
n | Tiskne poslední hodnotu v zásobníku a odstraní ji. |
P | Odstraní poslední hodnotu v zásobníku. |
f | Tiskne obsah zásobníku od poslední hodnoty k první. |
c | Maže celý zásobník |
r | Vymění poslední 2 hodnoty v zásobníku. |
d | Duplikuje poslední hodnotu v zásobníku |
sbuffer | Maže poslední hodnotu v zásobníku a zapíše ji do bufferu buffer. Buffery můžeme označovat jednotlivými znaky. |
lbuffer | Vyvolá hodnotu z bufferu a přidá ji na konec zásobníku |
hodnota>buffer | Přidá do bufferu hodnotu |
K dispozici jsou standardní operace: +, -, *, /, % (modulus), ^ (mocnina), v (odmocnina).
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říkaz | Význam |
k | Poslední hodnotu ze zásobníku odebere a určí podle ní přesnost (počet míst za desetinnou čárkou) |
K | Vrací 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říkaz | Význam |
i | Poslední hodnotu ze zásobníku odebere a určí podle ní soustavu pro vstup |
o | Poslední hodnotu ze zásobníku odebere a určí podle ní soustavu pro výstup |
I | Vrací aktuální soustavu, ve které čte čísla. Objeví se na konci zásobníku. |
O | Vrací aktuální soustavu, ve které tiskne čísla. Objeví se na konci zásobníku. |
Další příkazy:
Příkaz | Význam |
Z | Délku čísla (počet znaků, kterými je číslo napsáno. Desetinná tečka se nepočítá) přidá do zásobníku. |
z | Dé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. |
q | Konec |
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
$
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.
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