Näytä kirjoitukset

Tässä osiossa voit tarkastella kaikkia tämän jäsenen viestejä. Huomaa, että näet viestit vain niiltä alueilta, joihin sinulla on pääsy.


Viestit - petteriIII

Sivuja: 1 2 3 [4] 5 6 ... 33
61
BASH:issa ei ole desimaalilukuja - on vain tekstijonoja joista jotkut voi tulkita desimaaliluvuiksi. Tekstijonoa täytyy usein käsitellä jotenkin ennen tulostamista. Olisi houkuttelevaa käyttää tulostamiseen printf-komentoa sillä se kykenee käsittelemään tulostamisen yhteydessä ja se tulostaa desimaaliluvutkin oikein. Mutta se toimii vain kun kokonaisosan ja desimaaliosan yhteenlaskettu numeromäärä on BASH:issa  alle 19, se vaatii joskus desimaalieroittimeksi sen mikä koodisivulla on määrätty (pilkku tai piste) - ja lisäksi se on pikkuisen hidas. Ei voi olla ajattelematta että prntf on heitetty BASH:in rattaisiin jottei desimaalilaskentaa saisi toimimaan hyvin.

Käytettäessä echo:a tulostettavissa luvuissa saa olla moninkertainen määrä numeroita eikä desimaalieroittimen kanssa joudu tappelemaan. Mutta echo ei kykene käsittelemään tulostettavaa mitenkään vaan se tarvitsee apua BASH-skripteiltä ja nämä ovat usein erittäin kookkaita - mutta senlaatuiset skriptit ovat myös hyvin nopeita.

Desimaalilaskennan skriptit ovat vikaherkkiä senlisäksi että ne ovat runsaskoodisia. Ja sellaisissa tilanteissa käytetään normaalisti kirjastoja koodinteossa mutta virtuoosit ovat suhmuroineet BASH:in kirjastoja niin paljon ettei BASH:in kirjastoja juuri kukaan enää käytä. Sentakia on helppoa uskotella että esimerkiksi BASH ei kykene juuri ollenkaan numeronmurskaukseen ilman ulkoisten ohjelmien apua, joten esimerkiksi desimaalilaskentaa pidetään mahdottomana.

Kirjasto on yksinkertaisimmassa muodossaan tiedosto johon on kirjoitettu funktioita. BASH:issa jonkun toisen tiedoston funktiot voi liittää tehtävän skriptin koodiin käskyllä: . tiedoston nimi. Liitoskäsky on tosiaankin pelkkä piste. Aikoinaan BASH:illa oli moniakin kirjastoja - netistä löytyy vieläkin aikamonta (mutta niiden löytäminen on hankalaa). Mutta tapa liittää kirjasto  tehtävän skriptin koodiin oli alussa toisenlainen kuin nykyään. Kun liitäntätapaa muutettiin niin kirjastoja lakattiin käyttämästä - vaikka ne toimisivat edelleenkin.

Kirjastofunktion testaaminen on huomattavasti isompi työ kuin tavallisen funktion vaikka niillä ei teoriassa juuri eroa ole - mutta kirjastofunktion täytyy varautua paljon paremmin niihin tapauksiin joita pidetään puolimahdottomina. Jokatapauksessa jokaisessa kirjastofunktiossa on alkuunsa useimmiten naurettaviakin puutteita ja virheitäkin - ja läheskaikkien korjaaminen olisi vuosien urakka komppanialta koodaajia.

Esimerkiksi seuraava tosinopeatoiminen funktio jota kehoitetaan katkaisemaan annettu desimaaliluku määrättävän desimaalimäärän jälkeen pyöristäen oikein. Funktion koodi on nopea huolimatta pituudestaan - eikä tällaista funktiota varmaankaan kukaan viitsisi tehdä jos sitä ei löydy kirjastosta:
Koodia: [Valitse]
function pyöristä_desimaaleissa () { (( ${1//./} > 0 )) && { (( $# == 2 )) && desimaalienluku=$2 || desimaalienluku=0; kokonaisosa=${1%%[,.]*}; desimaaliosa=${1##*[,.]}; (( $desimaalienluku == 0 )) && echo $(($kokonaisosa+$((${desimaaliosa:0} >= 50)))) && return; desimaaliosa=$desimaaliosa"0000000000000000000000";echo $kokonaisosa.$(( ${desimaaliosa:0:$desimaalienluku}+$(( ${desimaaliosa:$desimaalienluku+1} >= 50)) )) ;} || { (( $# == 2 )) && desimaalienluku=$2 || desimaalienluku=0; kokonaisosa=${1%%[,.]*}; desimaaliosa=${1##*[,.]}; (( $desimaalienluku == 0 )) && echo $(($kokonaisosa+$((${desimaaliosa:0} <= 50)))) && return; desimaaliosa=$desimaaliosa"0000000000000000000000";echo $kokonaisosa.$(( ${desimaaliosa:0:$desimaalienluku}-$(( ${desimaaliosa:$desimaalienluku+1} <= 50)) )) ;} ;}

# kutsuesimerkit testaamiseen (kokeillessa leikkaa-liimaaa kaikki päätteeseen yhtenä palasena):
pyöristä_desimaaleissa 1.51515773 0   # pitäisi tulostaa 2
pyöristä_desimaaleissa 1.51515773     # pitäisi tulostaa 2
pyöristä_desimaaleissa 1.51515773 2   # pitäisi tulostaa 1.52
pyöristä_desimaaleissa 12345678901234567890123456789.51515773 2 # pitäisi tulostaa
                                                                #12345678901234567890123456789.52
pyöristä_desimaaleissa 1.51515773 3   # pitäisi tulostaa 1.515
pyöristä_desimaaleissa 1.51515773 4   # pitäisi tulostaa 1.5152
pyöristä_desimaaleissa 1.5151577311111151 14 # pitäisi tulostaa 1.51515773111112
pyöristä_desimaaleissa -1.51515773 0  # pitäisi tulostaa -1
pyöristä_desimaaleissa -1.51515773 2  # pitäisi tulostaa -1.51
pyöristä_desimaaleissa -1.51515773 3  # pitäisi tulostaa -1.514
pyöristä_desimaaleissa -1.51515773 4  # pitäisi tulostaa -1.5151
pyöristä_desimaaleissa 1.1 9          # pitäisi tulostaa  1.100000000
:

62
Muuttujan julistaminen (=declare) kokonaisluvuksi kannattaa, sillä koodi yksinkertaistuu ja nopeutuukin aikapaljon. Käskyt näiden toteamiseksi:
 
time { declare -i x; x=0; for n in {1..100000}; do x=x+1; done; echo $x ;}
time { unset x; x=0; for n in {1..100000}; do x=$((x+1)); done; echo $x ;}
time { for n in {1..100000}; do : ; done ;} # tällä saa sen ajan minkä itse looppi vie

- käsky: x=x+1 tallettaa muistipaikkaan x tekstijonon x+1 ja tuo "declare -i x" määrää BASH-tulkin tulkkaamaan sen kokonaisluvuksi. Näet tämän kun kirjoitat:
Koodia: [Valitse]
unset x; for n in {1..100000}; do x=x+1; done; echo $x
- se että käskyrivin eteen täytyy kirjoittaa "unset x" johtuu siitä että BASH-tulkki noudattaa samassa päätteessä aikaisemmin annettua määräystä ellei uutta määräystä ole annettu. Käsky "unset x" poistaa määräyksen.
- tämä vanhojen asetuksien muistaminen saattaa joskus olla paha asia silloin kun yrittää opiskella BASH:in saloja. Tämän takia omat skriptit saattavat toimia silloinkin kun ei pitäisi ja toisten tekemät skriptit eivät toimi vaikka pitäisi.

63
Useimmille matematiikan funktioille on monia erilaisia sarjakehitelmiä. Ja luonnollista logaritmia ei BASH:issa voi laskea samallatavalla kuin 10-kantainen logaritmi laskettiin vaan se täytyy laskea sarjakehitelmällä koska BASH:issa korottaminen potenssiin 2.71... on vaikeaa. Tässäkäytetty sarjakehitelmä toimii tosin vain kun logaritmoitava on suurempi kuin .5 . Vaikka teoriassa ylärajaa ei ole niin aikaa alkaa kulua kun logaritmoitava on suurempi kuin 100.

Tähänmennessä tehdyillä työkaluilla saa luonnolliseen-logaritmiin vain tusinan oikeita numeroita ja sekin vaati että työkaluihin tehtiin koodiparannuksia - sillä uudentyyppiset tehtävät tuovat näin alussa esiin uusia ongelmia. Koodiparanneltujen työkalujen tulee toimia myös vanhoissa tehtävissä.

Tämä on sellaista numeroleikkiä jota harrastettiin vuosikymmeniä sitten samaanaikaan kun tietoisena BASH:in matemaattisista kyvyistä vakuutettiin silmät sinisinä ettei BASH osaa. Ja koskei alkeita selvitetty silloin aikoinaan täytyy ne yrittää selvittää nyt - tai eihän se enää kannata vaikka mielenkiintoista onkin.

Käytetyn matemaattisen sarjan "sanallinen" kuvaus:
apu=(logaritmoitava-1)/logaritmoitava; lnx=apu+(1/2)*apu^2+(1/3)*apu^3+(1/4)*apu^4 ...
- siis tässä tarvitsee laskea luvun korottamista potenssiin luokkaa sata.

Sanallisesta kuvauksesta tehty koodi: ensiksi lasketaan apu muuttujaan ja apu:n potenssien arvot matriisiin. Sarja suppenee eri luvuilla kovin erinopeasti joten lasketaan matriisiin uusia jäseniä niin kauan että laskuista saadut merkitsevät numerot ovat kaikki nollia - matriisin koosta ei tarvitse välittää sillä BASH:in matriisit kasvavat automaattisesti jäseniä lisättäessä:
Koodia: [Valitse]
function kerro18 () {
tulosta=: # jos halutaan tulostaa niin merkitään: tulosta='echo -e' ja jos ei niin tulosta=:
[[ ${1//[^.]/} ]] && luku1=${1:0:17} || luku1=${1:0:17}".0"
[[ ${2//[^.]/} ]] && luku2=${2:0:17} || luku2=${2:0:17}".0"
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=${luku1//-/}; luku2=${luku2//-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.}; desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2}))
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1'\r\v'
luku2=${luku2: -36} ; $tulosta $luku2'\r\v'
luku3=${luku3: -36} ; $tulosta $luku3'\r\v'
luku4=${luku4: -36} ; $tulosta $luku4'\r\v'
luku11=${luku1:0:18} # tämänjälkeen 18->17
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12'\r\v'
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22'\r\v'
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32'\r\v'
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42'\r\v'
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1'\r\n'
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1'\r\n'
summa2=000000000000000000$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); summa2=${summa2: -18}; $tulosta summa2:$summa2'\r\n'
#summa2=$(($m1*$m2*10#$summa2))
(( $m1 != $m2 )) && echo -n '-' 
apu=$summa2$summa1; (( $desimaaleja )) && echo $((${apu:0: -$desimaaleja})).${apu: -$desimaaleja} || { echo $(( 10#$summa2 ))$summa1 ;} ;}

function jaa () {
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=${luku1//-/}; luku2=${luku2//-/}
desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"0000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"0000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}
luku1ilmandesimaalipistetta=$kokonaisosa1$desimaaliosa1 #; echo $desimaaliosa1
luku2ilmandesimaalipistetta=$kokonaisosa2$desimaaliosa2 #; echo $desimaaliosa2
kokonaiset=$((10#$luku1ilmandesimaalipistetta/10#$luku2ilmandesimaalipistetta))
jakojaannos=$((10#$luku1ilmandesimaalipistetta%10#$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=000000000000000$desimaalit
kokonaiset=$kokonaiset.${desimaalit: -9}
# echo $jakojaannos
jakojaannos=$(((100000000*$jakojaannos)%$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=0000000000000000$desimaalit
(( $m1 != $m2 )) && echo -n '-'
echo $kokonaiset${desimaalit: -8} ;}

function yhteenlasku () { # voi käyttää vähennyslaskuunkin merkki!
[[ ${1//[^-]/} ]] && m1=- || m1=+; [[ ${2//[^-]/} ]] && m2=- || m2=+
luku1=${1:0:18}
luku2=${2:0:18}
luku1=${luku1//[-+]/}; luku2=${luku2//[-+]/}
luku1=${luku1//-./-0.}; luku2=${luku2//-./-0.}
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellainen mikäli ei jo ole
desimaaliosa1=${luku1##*.}; desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}; desimaaliosa2pituus=${#desimaaliosa2}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo a$desimaaliosa2' 'b$desimaaliosa1 ; read # testatessa tämä on tarpeen
desimaaleja=${#desimaaliosa1} #; echo $desimaaleja
kokonaisluku1=${luku1%%.*}
kokonaisluku2=${luku2%%.*}
kokoluku1=$kokonaisluku1$desimaaliosa1 #; echo $kokoluku1
kokoluku2=$kokonaisluku2$desimaaliosa2 #; echo $kokoluku2
case $m1$m2 in 
  +- )  luku=$((10#$kokoluku1-10#$kokoluku2))  ;;
  -+ )  luku=$((-10#$kokoluku1+10#$kokoluku2)) ;;
  ++ )  luku=$((10#$kokoluku1+10#$kokoluku2))  ;;
  -- )  luku=$((-10#$kokoluku1-10#$kokoluku2)) ;;
esac ;
echo ${luku:0: -$desimaaleja}.${luku: -$desimaaleja} ;}

unset matriisi
logaritmoitava=4.214 # kirjoita tähän se luku josta haluat luonnollisen logaritmin.
matriisi[1]=$(jaa $(yhteenlasku $logaritmoitava -1) $logaritmoitava); apu=${matriisi[1]}; time for n in {2..10000}; do matriisi[$n]=$(kerro18 $apu ${matriisi[$n-1]}); (( 10#${matriisi[$n]:3:16} == 0 )) && echo $n && break; printf "%s\n" "${matriisi[@]}"; done

# sitten matriisista lasketaan luonnollinen logaritmi:
lnx=0; for (( n=1; n<=${#matriisi[@]}; n++ )); do
apu=$(kerro18 $(jaa 1 $n) ${matriisi[n]})
lnx=$(yhteenlasku $lnx $apu); echo $lnx; done

64
Puolitus-funktiosta on tullut jo kaksi uutta versiota sillä vanhat versiot toimivat joissain erikoistilanteissa väärin. Desimaalilaskennan osalta en edes yritä laskea versioita. Jokaisessa skriptissä on varaa parantaa elikä tehdä siitä uusi versio. Joskus uusi versio on hitaampi mutta joskus myös nopeampi kuin vanha versio - ja koodi yksinkertaistuu melkein aina.

BASH:in desimaalilaskenta toimi jo ennenkuin bc:tä awk:ia oli. Mutta koska virtuoosit uskottelivat ettei BASH desimaalilaskentaan kykene niin BASH:ia ei enää edes kehitetty "koska matematiikka toimii kelvottoman huonosti". Ja kun kieltä ei kehitetä niin käytännössä se taantuu. Todellisuudessa BASH on "vähänumeroisissa nelilaskintyyppisissä desimaalilaskuissa"  huomattavasti nopeampi kuin mikään ulkoinen ohjelma kutem esimerkiksi bc tai awk.

Kehittyneemmissä laskuissa ja varsinkin "antilogaritmin laskemis-tyyppisissä" BASH on laskennallisesti erittäin hidas. Silti noita laskuhirviöitä kannattaa tehdä sillä ne eivät toimisi jos missään olisi ongelmia: jokaisen funktion ja funktioiden välisen tiedonsiirron tulee toimia moitteetta eivätkä funktiot saa häiritä toisiaan. Siis jos nuo laskuhirviöt toimivat niin se takaa laadun.

Sekin alkaa näkyä että vaikka BASH:in merkinnät ovat tyystin erilaisia kuin muissa kielissä niin ajatukset ovat samoja elikä ongelmat on kyllä tunnettu jo aikoinaan.

***

Antilogaritmin laskemiseksi ei ole samantapaista menetelmää kuin logaritmin laskemiseksi, joten antilogaritmin arvo on etsittävä binäärihaulla vertaamalla lukuarvion logaritmia annettuun lukuun.

Selvitettävää numeroa kohti binäärihaku täytyy suorittaa noin kolmesti joten pyrittäessä tarkkuuteen 14 numeroa binäärihakuja tulee noin 47 elikä homma kestää peruskoneella noin 6-16 sekuntia. Tajuttoman pitkään siis, mutta verrattuna siihen ettei se toimisi ollenkaan on ero valtava. Sitäpaitsi mikäli BASH:iin saisi pieniä kielellisiä parannuksia toisivat ne nopeutta tuhatkertaisesti.

Esimerkkinä antilogaritmista lasketaan minkä luvun logaritmi on: 0.30102999566398119:
Koodia: [Valitse]
  #!/bin/bash
function puolita () {
[[ ${1//[^-]/} ]] && merkki=- || merkki=''
luku=${1//-/}
[[ ${luku//[^.]/} ]] && luku=${luku:0:18} || luku=${luku:0:18}"."
[[ ${luku:0:1} == '0' ]] && luku=${luku:1}
desimaaliosa=${luku##*.}
kokonaisosa=${luku%%.*}
kokonaisosanpituus=${#kokonaisosa}
kokonaisosa='2'$kokonaisosa
lukuilmandesimaalipistetta=$kokonaisosa$desimaaliosa
kokonaiset=$(($lukuilmandesimaalipistetta/2))
jakojaannos=$(($lukuilmandesimaalipistetta%2))
kokonaiset=${kokonaiset:1}$(((10*$jakojaannos)/2))
echo $merkki$(( ${kokonaiset:0:$kokonaisosanpituus} )).${kokonaiset:$kokonaisosanpituus} ;} 

function kerro18 () {
tulosta=: # vaihtoehdot: tulosta=echo ja tulosta=:
[[ ${1//[^.]/} ]] && luku1=${1:0:17} || luku1=${1:0:17}".0"
[[ ${2//[^.]/} ]] && luku2=${2:0:17} || luku2=${2:0:17}".0"
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=${luku1//-/}; luku2=${luku2//-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.}; desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2}))
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18} # tämänjälkeen 18->17
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2
summa2=$(($m1*$m2*10#$summa2))
(( ! summa2 )) && summa1=$(($m1*$m2*10#$summa1))
apu=$summa2$summa1; (( $desimaaleja )) && echo $((10#${apu:0: -$desimaaleja})).${apu: -$desimaaleja} || { echo $(( 10#$summa2 ))$summa1 ;} ;}

function getlog ()  { # varsinainen logaritmointi
apu=${1//,/.}
[[ ${apu//[^.]/} ]] && vanhalogaritmoitava=$1 || vanhalogaritmoitava=$1".0"
logaritmoitavankokonaisosa=${vanhalogaritmoitava%%.*}
logaritmi=0; logaritmi=$((${#logaritmoitavankokonaisosa}-1)).
# sitten logaritmiin lisätään numeroita yksi kerrallaan mikäli siinä ei vielä ole tarpeeksimontaa numeroa:
until (( ${#logaritmi} >= 14 )); do
  apu=${vanhalogaritmoitava//./}; apu=${apu:0:1}.${apu:1}; # echo apu:$apu
  uusilogaritmoitava=1.0;
  for n in {1..10}; do uusilogaritmoitava=$(kerro18 $apu $uusilogaritmoitava); done
  # echo uusilogaritmoitava:$uusilogaritmoitava; read
  uudenlogaritmoitavankokonaisosa=${uusilogaritmoitava%%.*}
  logaritmi=$logaritmi$((${#uudenlogaritmoitavankokonaisosa}-1))
  vanhalogaritmoitava=$uusilogaritmoitava
done
echo $logaritmi ;}

function onkoekasuurempi  () {
[[ ${1//[^-]/} ]] && m1=a || m1=b; [[ ${2//[^-]/} ]] && m2=a || m2=b
luku1=${1//[-+]/}; luku2=${2//[-+]/} # etumerkki omaan muuttujaan ja vertailtavista pois

while [[ ${luku1:0:1} == 0 ]]; do luku1=${luku1:1}; done ; while [[ ${luku2:0:1} == 0 ]]; do luku2=${luku2:1}; done # etunollien poistaminen
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellaien mikäli ei jo ole
(( $# )) && luku1=${luku1//[^0-9]/} && luku2=${luku2//[^0-9]/}
kokonaisosa1=${luku1%%[,.]*}; desimaaliosa1=${1##*[,.]}
kokonaisosa2=${luku2%%[,.]*}; desimaaliosa2=${2##*[,.]}
case $m1$m2 in 
  ba )  echo 1 ;;
  ab )  echo 0 ;;
  bb )  [[ $kokonaisosa1 > $kokonaisosa2 ]] && { echo 1 || echo 0 ;return ;} || [[ $desimaaliosa1 > $desimaaliosa2 ]] && echo 1 || echo 0 ;;
  aa )  [[ $kokonaisosa1 > $kokonaisosa2 ]] && { echo 0 || echo 1 ;return ;} || [[ $desimaaliosa1 > $desimaaliosa2 ]] && echo 0 || echo 1 ;;
esac ;}

function yhteenlasku () { # voi käyttää vähennyslaskuunkin
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=$1
luku2=$2
luku1=${luku1//[-+]/}; luku2=${luku2//[-+]/}
luku1=${luku1//-./-0.}; luku2=${luku2//-./-0.}
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellainen mikäli ei jo ole
desimaaliosa1=${luku1##*.}; desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}; desimaaliosa2pituus=${#desimaaliosa2}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo a$desimaaliosa2' 'b$desimaaliosa1 ; read # testatessa tämä on tarpeen
desimaaleja=${#desimaaliosa1} #; echo $desimaaleja
kokonaisluku1=${luku1%%.*}
kokonaisluku2=${luku2%%.*}
kokoluku1=$kokonaisluku1$desimaaliosa1 #; echo $kokoluku1
kokoluku2=$kokonaisluku2$desimaaliosa2 #; echo $kokoluku2
(( $m2 +1 )) && luku=$((10#$kokoluku1+10#$kokoluku2)) || luku=$((10#$kokoluku1-10#$kokoluku2))
echo ${luku:0: -$desimaaleja}.${luku: -$desimaaleja} ;}

# laskennan määrittelyt:
annettuluku=0.30102999566398119 # Kirjoita tähän mistä antilogaritmi lasketaan - mitään muuta ei muuteta
tulosarvio=$((10**$((${annettuluku%%.*}+1))/5))
delta=$(puolita $tulosarvio)

# sitten binäärihaku:
time for n in {1..45}; do
echo 'kierros: '$n'  $tulosarvio ' $(getlog $tulosarvio)'  delta: '$delta       
(( $(onkoekasuurempi $(getlog $tulosarvio) $annettuluku ))) && tulosarvio=$(yhteenlasku $tulosarvio -$delta) || tulosarvio=$(yhteenlasku $tulosarvio $delta)
  delta=$(puolita $delta)
  delta=${delta:0:18}  # onko oikea paikka?   
done
echo $annettuluku on luvun: ${tulosarvio:0:12}  logaritmi 

65
Vaikka juuri koskaan ei saa aikaiseksi BASH-skriptiä joka ei alussa hölmöilisi niin ne hölmöilyt saa aina loppumaan. Olenkin aina kuvaillut tulevaa skriptiä ennenkuin oleellisimpia funktioita on tehty - ja jokakerran BASH saa asiat toimimaan. Toivottoman hidashan BASH on mutta kyllä BASH:illakin koodaten hautaan kerkiää - sitäpaitsi olen huomannut että kun teen uudestaan skriptin joka aikoinaan kesti sekunteja niin nyt se kestää millisekunteja. 

***   

Nyt antilogaritmin kimppuun:
 
"antilogaritmin peukalosääntö": luvun 10 logaritmi on 1; kääntäen: luvun 1 antilogaritmi on 10
- antilogaritmia tarvitaan esimerkiksi kun lasketaan: luku^murtoluku:
esimerkiksi 2^2.1  lasketaan: antilogaritmi $( 2.1*$(logaritmi 2))
- mutta varsinainen syy tämän skriptin tekemiseen on testata desimaalilaskentaa ja oppia sen sääntöjä.

Antilogaritmin laskemisessa erikoisinta on siinä oleva funktio luvun puolittamiseen sillä sen täytyy ottaa nopeasti huomioon:
- puolitettava on aina joko kokonaisluku tai desimaaliluku typiltään: x.xxxxxxxxxxxxxxxxxxxxxx jossa x kuuluu joukkoon 0-9 tai tyhjä.
- joten siinä tulee eteen kummankin tyypin oktaaliansa: kokonaisluku saattaa olla nolla ja oktaaliansa laukeaisi jos sitä ei väistelisi; laskettaessa poistaisi ja tulostettaessa palauttaisi.
- mutta mikäli desimaaliosankin alussa on nollia niin tätä käänteistä oktaaliansaa ei edes voi poistaa. Jotta ei jouduttaisi oktaaliansaan lisätään desimaaliosan eteen luku 2 ja tulostettaessa se poistetaan - silloinhan se on edelleen tulostettavan alussa ja sen arvo on 1.

puolittamisen toteuttava funktio:
Koodia: [Valitse]
#!/bin/bash
function puolita () {
[[ ${1//[^.]/} ]] && luku=${1:0:18} || luku=${1:0:18}".0"
luku=${luku//0./.}
desimaaliosa=${luku##*.}
kokonaisosa=${luku%%.*}
kokonaisosanpituus=${#kokonaisosa}
(( $kokonaisosa )) && etunolla='' || etunolla='0'
kokonaisosa='2'$kokonaisosa # desimaaliosasta on tullut kokonaisosa
lukuilmandesimaalipistetta=$kokonaisosa$desimaaliosa
 
kokonaiset=$(($lukuilmandesimaalipistetta/2))
jakojaannos=$(($lukuilmandesimaalipistetta%2))
desimaalit=$(((1000000000*$jakojaannos)/2))
desimaalit=000000000000000$desimaalit
kokonaiset=$etunolla${kokonaiset:1}${desimaalit: -9}
 
echo $(( 10#${kokonaiset:0:$kokonaisosanpituus} )).${kokonaiset:$kokonaisosanpituus} ;}

66
Sanallinen esitys kymmenkantaisen logaritmin laskemisesta:
- tässä skriptissä alkuoletus on että logaritmoitava on yli 1.
- kun puhutaan numerosta niin tarkoitetaan yhtä merkkiä: 1, 2, 3, 4, 5, 6, 7, 8, 9 tai 0.
- toiminta ei muutu mitenkään vaikka logaritmoitava olisin kokonaisluku ilman desimaaleja. 
- aluksi logaritmi nollataan. Sitten logaritmin ensimmäiseksi numeroksi tulee "logaritmoitavan kokonaisosan numeroiden lukumäärä -1" ja perään tulee desimaaipiste.

Sitten logaritmiin lisätään numeroita yksi kerrallaan niin monta kertaa kuin tulokseen halutaan desimaaleja: logaritmin seuraava numeron selvittämiseksi lasketaan uusi logaritmoitava siirtämällä desimaalipiste  vanhassa logaritmoitavassa ensimmäisen numeron perään ja korottamalla saatu luku potenssiin kymmenen. Logaritmin seuaava numero on "logaritmoitavan kokonaisosan numeroiden määrä -1". Uudesta logaritmoitavasta tulee vanha logaritmoitava. Palataan kohtaan: Sitten logaritmiin lisätään ...

Koodina tämä on:
Koodia: [Valitse]
#!/bin/bash
function kerro18 () {
tulosta=: # vaihtoehdot: tulosta=echo ja tulosta=:
[[ ${1//[^.]/} ]] && luku1=${1:0:17} || luku1=${1:0:17}".0"
[[ ${2//[^.]/} ]] && luku2=${2:0:17} || luku2=${2:0:17}".0"
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=${luku1//-/}; luku2=${luku2//-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.}; desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2}))
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18} # tämänjälkeen 18->17
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2
summa2=$(($m1*$m2*10#$summa2))
(( ! summa2 )) && summa1=$(($m1*$m2*10#$summa1))
apu=$summa2$summa1; (( $desimaaleja )) && echo $((10#${apu:0: -$desimaaleja})).${apu: -$desimaaleja} || { echo $(( 10#$summa2 ))$summa1 ;} ;}

function getlog ()  { # varsinainen logaritmointi
apu=${1//,/.}
[[ ${apu//[^.]/} ]] && vanhalogaritmoitava=$1 || vanhalogaritmoitava=$1".0"
logaritmoitavankokonaisosa=${vanhalogaritmoitava%%.*}
logaritmi=0; logaritmi=$((${#logaritmoitavankokonaisosa}-1)).
# sitten logaritmiin lisätään numeroita yksi kerrallaan mikäli siinä ei vielä ole tarpeeksimontaa numeroa:
until (( ${#logaritmi} >= 19 )); do
  apu=${vanhalogaritmoitava//./}; apu=${apu:0:1}.${apu:1}; # echo apu:$apu
  uusilogaritmoitava=1.0;
  for n in {1..10}; do uusilogaritmoitava=$(kerro18 $apu $uusilogaritmoitava); done
  # echo uusilogaritmoitava:$uusilogaritmoitava; read
  uudenlogaritmoitavankokonaisosa=${uusilogaritmoitava%%.*}
  logaritmi=$logaritmi$((${#uudenlogaritmoitavankokonaisosa}-1))
  vanhalogaritmoitava=$uusilogaritmoitava
done
echo luvun: $1 '  logaritmi on: '$logaritmi ;}

getlog 2.0000001234


Pitäisi tulla: luvun: 2.0000001234   logaritmi on: 0.301030022459
#                                                           tulee: 0.301030022459 


Onhan tämä suunnattoman hidasta eikä tarkkuuskaan riitä kuin tusinaan numeroita. Mutta se on osoitus siitä mihin BASH kykenee 19-numeron kokonaislukukertolaskullaan ja ennenkaikkea osoitus ettei pidä sanoa: BASH ei osaa .....

Jokaisen skriptin kykyjen huonouteen pätee: kun joku esittää jotakin niin täysin varmasti jokutoinen pistää paremmaksi. Tai nykyäänhän se ei enää pidä paikkaansa koska virtuoosit ovat saaneet kaikki uskomaan että BASH on paitsi kelvoton niin myös kyvytön.

67
BASH:issa on aivan varmaa että kun joku tekee jostain asiasta hitaan satarivisen skripti-hirviön niin joku tekee samasta asiasta muutaman rivinpituisen nopeasti toimivan toteutuksen ja hirviön tekijä saa trauman loppuiäkseen. Mutta jos tätä ei hyväksytä niin BASH:ia ei kukaan uskalla kehittää - sillä niitä lyhyitä ja nopeita versioita ei tehdä ensiksi sillä vain mikäli tuo hirviöskripti toimii uskotaan etteivät puheet BASH:in kyvyttömyydestä pidä paikkaansa ja kannattaa yrittää - tai eihän nykyään enää yritetä sittenkään koska väitteet BASH:in kyvyttömyydestä on omaksuttu liiankin hyvin.

***

Valmistautumista logaritmin laskemiseen - tämä on se hidas hirviö jonka täytyy toimia ennenkuin on mahdollista tehdä se nopea versio - vaikka eihän BASH:ista saa edes kohtuunopeaa matemaattisissa operaatioissa.

Kymmenkantaista logaritmia laskettaessa suurin haaste on logarotmoitavan desimaaliluvun korottaminen potenssiin kymmenen. BASH:issa kymmenpotenssiinkorotus täytyy toteuttaa kertomalla luku itsellään kymmenenkertaa sillä merkkiluku eksponentiaatiossa on paljon pienempi. BASH:in oman kertolaskunkaan numeromäärä ei ole riittävä vaan tarvitsee käyttää kaksinkertaisen numeromäärän desimaalilukujen kertolaskuskriptiä. Kymmenpotenssiin korottaminen tällätavoin kestää noin 65ms ja lopputuloksessa on noin 14 oikeaa numeroa:
Koodia: [Valitse]
#!/bin/bash
# Kertolaskussa kumpikin jäsen saa olla 17numeroinen ja tulokseen voi tulla 34numeroa. Myös etumerkki
# hyväksytään niinkuin myös desimaalit ja silti toimintanopeus on 2ms. Onhan se pitkä aika yhdelle
# kertolaskulle, mutta onnistuu sentään. Tätä versiota on korjailtu runsaasti.

function kerro18 () {
tulosta=: # vaihtoehdot: tulosta=echo ja tulosta=:
[[ ${1//[^.]/} ]] && luku1=${1:0:17} || luku1=${1:0:17}".0"
[[ ${2//[^.]/} ]] && luku2=${2:0:17} || luku2=${2:0:17}".0"
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=${luku1//-/}; luku2=${luku2//-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.}; desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2}))
luku1=000000000000000000${luku1//./}
luku2=000000000000000000${luku2//./}
a=${luku1: -18:9}; b=${luku1: -9}
c=${luku2: -18:9}; d=${luku2: -9}; $tulosta $a' '$b; $tulosta $c' '$d
luku1=00000000000000000000000000000000000000$((10#$b*10#$d))
luku2=00000000000000000000000000000000000000$((10#$d*10#$a))"000000000"
luku3=00000000000000000000000000000000000000$((10#$c*10#$b))"000000000"
luku4=00000000000000000000000000000000000000$((10#$a*10#$c))"000000000000000000"
luku1=${luku1: -36} ; $tulosta $luku1
luku2=${luku2: -36} ; $tulosta $luku2
luku3=${luku3: -36} ; $tulosta $luku3
luku4=${luku4: -36} ; $tulosta $luku4; $tulosta
luku11=${luku1:0:18} # tämänjälkeen 18->17
luku12=${luku1:18}; $tulosta a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; $tulosta c$luku21' 'd$luku22
luku31=${luku3:0:18}
luku32=${luku3:18}; $tulosta a$luku31' 'b$luku32
luku41=${luku4:0:18}
luku42=${luku4:18}; $tulosta c$luku41' 'd$luku42;$tulosta
summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42)); $tulosta summa1:$summa1
summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus >= 19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
summa1=000000000000000000$summa1; summa1=${summa1: -18} ;$tulosta ylivuoto:$ylivuoto' summa1:'$summa1
summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto)); $tulosta summa2:$summa2
summa2=$(($m1*$m2*10#$summa2))
(( ! summa2 )) && summa1=$(($m1*$m2*10#$summa1))
apu=$summa2$summa1; (( $desimaaleja )) && echo $((10#${apu:0: -$desimaaleja})).${apu: -$desimaaleja} > /tmp/joo || { echo $(( 10#$summa2 ))$summa1 ;} ;}

# Potenssiin korotus on helpointa testata seuraavalla skriptillä: se tulostaa jokaisella kierroksella kertolaskusta saadun tuloksen ja seuraavalla rivillä bc:n varmasti oikean tuloksen. Muista laittaa potenssiin korotettava täysin samanlaisena kahteen paikkaan:
time { echo 1 > /tmp/joo; for n in {1..10}; do kerro18 9.87654321 $(cat /tmp/joo); cat /tmp/joo;
bc -l <<< "9.87654321^$n"; echo; done ;}

68
Päivitin neliöjuuren laskemis-skriptin, vaikkei sillä toistaiseksi olekaan muuta tarkoitusta kuin osoitaa että BASH:in matematiikkaa haukutaan osittain väärin perustein. Ja kukatietää mihin päädytään jatkuvien oleellisten parannuksien kanssa - nämäkin parannukset nopeuttivat kaksinkertaisesti. Ehkä BASH:in matematiikastakin tulee edes siedettävää?

Skripti laskee neliöjuuresta vain 8-9 numeroa mutta sen se tekee parhaimmillaan nopeammin kuin jos laskettaisiin neliöjuuri bc:ssä - elikä laskenta kestää 2ms:a.

Mutta neliöjuuren laskemiseen tätä ei ole tehty vaan virtuoosien toimien ihmettelyyn. Sillä nyt kun skriptillä on sujuvampi toiminta on varmaa että ihmettelyyn on aihetta. On mahdotonta uskoa että maailmanluokan virtuoosit eivät olisi tunteneet desimaalilaskennan saloja ikuisesti.

Jossain vaiheessa kiinnitän huomioni logaritmeihin. Niihinkin BASH ilmanmuuta kykenee olkoonkin etteivät BASH:in toistaiseksi selvinneet kyvyt kovin pitkälle riitä.
Koodia: [Valitse]
function sqrt () {
declare -i seed 
declare -i delta # nämä declare-määritelyt mahdollistivat koodimuutoksia ja normaalin-näköisen matematiikan. Koodi nopeutuikin melkein puolella.

function desimaalipiste2oikealle () { # juurrettava on <0
desimaaliosa=${1##*.}; # kokonaisosaahan ei voi ollakaan jos tänne on tultu.
(( ${desimaaliosa:0:1} )) && echo ${desimaaliosa:0:2}.${desimaaliosa:2} || echo ${desimaaliosa:1:1}.${desimaaliosa:2} ;}               

function  desimaalipiste2vasemmalle () { # juurettava on >100
kokonaisosa=${1%%.*}; desimaaliosa=${1##*.}
echo ${kokonaisosa:0: -2}.${kokonaisosa: -2}$desimaaliosa ;}
 
luku=$1 # funktio sqrt:n koodi alkaa
[[ $luku == 1 ]] && echo 1 && return
[[ ${luku:0:1} == . ]] && luku=0$luku
[[ ${luku//./} == $luku ]] && luku=$luku".0" || luku=$luku'0' # luvussa täytyy olla desimaalipiste
desimaalisiirto=0
while (( ${luku%%.*} <= 0 )); do luku=$( desimaalipiste2oikealle $luku) && (( desimaalisiirto-- )); done
# echo $desimaalisiirto' '$luku
while (( ${luku%%.*} >= 100 )); do luku=$(desimaalipiste2vasemmalle $luku) && (( desimaalisiirto++ )) ; done #; echo $desimaalisiirto' '$luku # toiminnantarkastamisessa hyvä vihje
in=${luku//./}"0000000000000000000"
in=${in:0:17}
seed=2010000000  # maksimi seed olisi 4294967295
delta=1005000000
apu=${luku%%.*}
(( ${#apu} == 1)) && {
for (( n=delta; n>=1; n=n/2 )); do [[ $seed*$seed -gt $in ]] && seed=seed-n || seed=seed+n; done ;} || { for (( n=delta; n>=1; n=n/2 )); do [[ $seed*${seed::-1} -gt $in ]] && seed=seed-n || seed=seed+n; done ;}
case  1:${desimaalisiirto:--} in # tulostus
($((desimaalisiirto<0))*) echo ${seed:0:1}.${seed:1}e$desimaalisiirto ;;
($((desimaalisiirto>8))*) echo ${seed:0:1}.${seed:1}e$desimaalisiirto ;;
                       *) echo ${seed:0:desimaalisiirto+1}.${seed:desimaalisiirto+1} ;;
esac ;}


# tarkistukset: sqrt 2.3456   # pitää tulla: 1.53153519
#               sqrt 23.456   # pitää tulla: 4.84313949
#               sqrt 234.56   # pitää tulla: 15.3153519
#               sqrt 0.023456 # pitää tulla: 1.53153519e-1
#               sqrt 1234567800000000000  # pitää tulla: 1.11111105e9
#               sqrt .000000000012345678  # pitää tulla: 3.51364171e-6

***

Desimaalimatematiikan skripteissä on usein monta funktiota. Kun joku funktio höpertää menee koko lasku metsään. Olisi helppo selvittää mikä funktio on syypää mikäli funktiot kirjoittaisivat näytölle välituloksiaan.

Mutta koska parametrit normaalisti siirretään näytön kautta menee vastaanottaja sekaisin kun ei erota mikä on välitulosta ja mikä parametria. Juuri tästä syystä haukutaan ettei BASH osaa parametrejaan palauttaa - mutta haukutaan syyttä kun ei haluta käsittää että parametrien palautustapoja on useampia.

Tässä parametrit siirretää suurinpiirtein samallatavoin kuin muissakin kielissä:
Koodia: [Valitse]
function koe () { apu=$(declare | grep ^$1=); muuttuja=${apu##*=}; muuttuja=$((2*muuttuja)); echo höh; read $1 < <(echo $muuttuja) ;}

# funktiokutsu:
luku=2; koe luku; echo $luku
selvitystä muuttujista:
apu=$(declare | grep ^$1=)  hakee BASH:in muutujalistasta muuttujaa vastaavan declare-lauseen.
                            Muuttujalista on nimittäin sama oltiinpa funktiossa tai pääohjelmassa.
                            Tällätavoin funktioon voidaan toimittaa haluttu määrä parametreja, myös
                            matriiseja. Samanaikaisesti voidaan käyttää sitä vanhaakin tapaa.
muuttuja=${apu##*=}         ottaa muuttujan arvon declare-lauseesta yhtäkuin-merkin perästä.
muuttuja=$((2*muuttuja))    kerrotaan muutuja kahdella jotta pääohjelmassa näkisi muuttujan muuttuneen.
read $1 < <(echo $muuttuja) muutetaan muuttujalistaa. Read, let tai readarray on pakollinen.
echo höh                    osoitus siitä ettei näytölle kirjoittaminen sotke laskua.
apu                         apumuuttuja jolla ei ole myöhemmin mitään merkitystä.
 
- parametri on tyypiltään nimiparametri eikä arvoparametri jollaisia BASH:in funktioissa on totuttu käyttämään. Muutujalle ei pidä laittaa funktiossa local-määrittelyä.
- palautettava parametri saa olla mitä tyyppiä hyvänsä, vaikka matriisi.
- voidaan palauttaakin niin monta parametria kuin halutaan ja mitä tyyppiä halutaan, myös matriiseja.

Hankalaahan tuo on, mutta yleensä tätä parametrien palautustapaa ei tarvitse käyttää.
- tuon declare-kompleksin sijaan voi kirjoittaa: eval apu=\$$parametrinumero. Silloin ei tarvita lausetta: apu=${apu##*=}
- jos pilkkua viilataan niin parametria ei palauteta vaan määritetään sen arvo globaalialueella.
- asia on esitetty aikaisemminkin mutta nyt se on esitetty täysin uudesta näkökulmasta.

69
Desimaalimatematiikan suorittaminen BASH:in omilla käskyillä vaatii runsaasti aputoimintoja. Useat aputoiminnot ovat niin nopeita että kun niistä kasaa funktion niin se ei hidasta havaittavasti. Joten niitä funktioita kannattaa tehdä - jos ei muusta syystä niin virtuooseja kurmuuttaakseeni sillä eihän näillä funktioilla toistaiseksi muuta merkitystä ole. Esimerkiksi:
Koodia: [Valitse]
function desimaaliosanloppunollatpois () {
kokonaisosa=${1%%[,.]*}; desimaaliosa=${1##*[,.]}; [ ${#desimaaliosa} -eq ${#1} ] && { echo $1; return ;}; desimaaliosa=.$desimaaliosa; while [[ ${#desimaaliosa} && ${desimaaliosa: -1} == [.0] ]]; do desimaaliosa=${desimaaliosa:0: -1}; done; echo $kokonaisosa$desimaaliosa ;}

luku=10.0102000; desimaaliosanloppunollatpois $luku


70

BASH:issa on useampiakin keinoja laskea e (se luonnollisen järjestelmän kantaluku joka on arvoltaan 2.71828....). Tässä annetuilla työkaluilla sen arvosta saa 16 ensimmäistä numeroa. Ei tällä tietenkään ole muuta käyttötarkoitusta kuin että käyhän tämäkin leikkikalusta ja sainhan tätä tehdessäni hihitellä BASH:ista esitetyille väitteille. Itseasiassa vain tuo viimeinen rivi on uutta - funktiothan on esitetty jo aikaisemmin. Aikaa kuluu e:n arvoa etsittäessä noin 70ms

e:n arvo saadaan laskulla: 1/0!+1/1!+1/2!+1/3!+1/4! ...  Ja sen laskemistahan tuo viimeinen lause ohjaa.

- ja nimenomaan se pännii että tämä olisi toiminut aina, jo silloin aikoinaan kun BASH:ia alettiin kelvottomaksi väittää.

Koodia: [Valitse]
function jaa () {
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"0000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"0000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

luku1ilmandesimaalipistetta=$kokonaisosa1$desimaaliosa1 #; echo $desimaaliosa1
luku2ilmandesimaalipistetta=$kokonaisosa2$desimaaliosa2 #; echo $desimaaliosa2
kokonaiset=$((10#$luku1ilmandesimaalipistetta/10#$luku2ilmandesimaalipistetta))
jakojaannos=$((10#$luku1ilmandesimaalipistetta%10#$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=000000000000000$desimaalit
kokonaiset=$kokonaiset.${desimaalit: -9}
# echo $jakojaannos
jakojaannos=$(((100000000*$jakojaannos)%$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=0000000000000000$desimaalit
echo $kokonaiset${desimaalit: -8} ;}     

function yhteenlasku () { # voi käyttää vähennyslaskuunkin
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=$1
luku2=$2
luku1=${luku1//[-+]/}; luku2=${luku2//[-+]/}
luku1=${luku1//-./-0.}; luku2=${luku2//-./-0.}
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellaien mikäli ei jo ole
desimaaliosa1=${luku1##*.}; desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}; desimaaliosa2pituus=${#desimaaliosa2}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo a$desimaaliosa2' 'b$desimaaliosa1 ; read # testatessa tämä on tarpeen
desimaaleja=${#desimaaliosa1} #; echo $desimaaleja
kokonaisluku1=${luku1%%.*}
kokonaisluku2=${luku2%%.*}
kokoluku1=$kokonaisluku1$desimaaliosa1 #; echo $kokoluku1
kokoluku2=$kokonaisluku2$desimaaliosa2 #; echo $kokoluku2
(( $m2 +1 )) && luku=$((10#$kokoluku1+10#$kokoluku2)) || luku=$((10#$kokoluku1-10#$kokoluku2))
echo ${luku:0: -$desimaaleja}.${luku: -$desimaaleja} ;}

summa=2; for n in {2..18}; do summa=$(yhteenlasku $summa $(jaa 1 $(($(seq -s* $n))))); done; echo $summa

71
Windows alkoi muutamavuosisiten liittämään BASH:ia koodiinsa. Olettekos ihmetelleet miksi tehdä hyvin kallis lisäys toiminnasta jota Linux-yhteisössä pidetään surkimuksena?

Johdatteleva kysymys: millä saralla Linux on ehdoton kunkku? Haluaisivatkohan muut osille noissa super-tietokoneissa? - ei se raha vaan maine. Valitettavasti taitavat onnistua kun ottaa huomioon kuinka Linux-virtuoosit BASH:ia kohtelee. Sillä BASH on surkimus ja samalla kuningaspultti.

BASH-skriptit ovat koneen hoitamisessa erinomaisia mutta harva enää nykyään konettaan hoitaa - se on noidankehä joka kokoajan kiihtyy ja päätyy orjan elämään kun kaikessa täytyy luottaa toisiin. Siis nimenomaan täytyy, ei voi valita sitä että tekisi jotain itse.

72
Skriptin toiminnan tutkimiseksi siihen kannattaa skriptiä tehdessä laittaa tulostus lähes kaikkelle mahdolliselle. Mutta ennenkaikkea niitä tulostuskäskyjä ripotellaan koodiin jonka toimintaa ei omalla logiikallaan hallitse ja skripitin toimintaa korjataan kokoajan. Toiminta on tällaista kaikilla kielillä.

Mutta välillä nuo välitulosteet sotkevat joten ne poistetaan ja hetkenkuluttua taas palautetaan. Melkoinen homma jos ei tee noista tulostamisista ehdollista joten yhdellä muuttujalla voi säätää tulostetaanko kaikissa miljoonassa paikassa vai ei. Mutta ne tulostusehdot ovat kookkaita ja skriptin rakenne vääristyy.   

Mutta BASH:issa ei tarvita ehtoa. Esimerkki kertonee parhaiten kuinka tehdään: skriptin alkuun kirjoitetaan:  tulosta=echo. Ja loppuskriptin echo:t korvataan muuttujalla $tulosta. Esimerkiksi:
tulosta=echo; $tulosta apua -> tulostaa: apua.

- skriptin merkityksellisin echo kyllä jätetään.
- kun ei enää haluta välitulosteita niin muuttujalle: tulosta annetaan arvo : (siis kaksoispiste)
- voi muuttujalle: tulosta  yhtähyvin antaa arvon: 'printf %s\n'
- bacup tai mikä muu hyvänsä hoituu samallatavoin.
- se siitä ensimmäisenluokan funktiosta jota BASH:issa ei ole.

73

Ensin rajoittamattoman monen rajoittamattoman moninumeroisten luvun yhteenlaskin jossa on pari funktiota ja neljä looppia. Tätä ei ole suoranaisesti tarkoitettu käytettäväksi vaan se on tehty funktiokutsujen ja looppien nopeuden varmistamiseksi. Lopussa kuvatulla laskulla skriptin läpikäyminen vaatii noin 150:n monipuolisen käskyn suorittamisen elikä noin 3ms/150=20 mikrosekuntia per käsky -> siis kaikki sen käskyt ovat nopeita.
Koodia: [Valitse]
function radd () { 

function desimaalisumma () { for((j=1;j<=$2;j++)); do summa=$((summa+${desimaaliosa[j]:$1:1})); done; [[ $muistinumero ]] || muistinumero=0;summa=$((summa+$muistinumero)); muistinumero=0 ;}

function kokonaissumma  () { for((j=1;j<=$2;j++)); do summa=$((summa+${kokonaisosa[j]:$1:1})); done; [[ $muistinumero ]] || muistinumero=0; summa=$((summa+$muistinumero)); muistinumero=0 ;}

i=1; maxdesimaalit=0; maxkokonaiset=0; muistinumero=0; for n in $@; do desimaaliosa[i]=${n##*.}; desimaaliosanpituus[i]=${#desimaaliosa[i]}; (( ${desimaaliosanpituus[i]} >= $maxdesimaalit )) && maxdesimaalit=${desimaaliosanpituus[i]}; kokonaisosa[i]=${n%%.*};  kokonaisosanpituus[i]=${#kokonaisosa[i]}; (( ${kokonaisosanpituus[i]} >= $maxkokonaiset )) && maxkokonaiset=${kokonaisosanpituus[i]}; (( i++ )); done

i=1; for n in $@; do desimaaliosa[i]=${n##*.}"000000000000000000"; desimaaliosa[i]=${desimaaliosa[i]:0:$maxdesimaalit};
kokonaisosa[i]="000000000000"${kokonaisosa[i]}; kokonaisosa[i]=${kokonaisosa[i]: -$maxkokonaiset};
echo ${kokonaisosa[i]}.${desimaaliosa[i]}; (( i++ )); done 

desimaalit=''; for((i=maxdesimaalit-1;i>=0;i--)); do summa=0; desimaalisumma i $# ; desimaalit=${summa: -1}$desimaalit; muistinumero=${summa:0: -1}; done; # echo $desimaalit' '$muistinumero; read

kokonaiset=''; for((i=maxkokonaiset-1;i>=0;i--)); do summa=0; kokonaissumma i $# ; kokonaiset=${summa: -1}$kokonaiset; muistinumero=${summa:0: -1}; done; echo $muistinumero$kokonaiset.$desimaalit
}

time radd 1.17 22.222 3.33 4.4


74
BASH:ista haastellaan potaskaa kaikkialla: osoitus siitä että BASH hallitsee ohjelmarakenteen nimeltään: first-class function
Koodia: [Valitse]
function koe () { local numero; $1 $2 numero; echo $numero ;}   
function joo () { readarray $1 < <(echo 12345) ;}
a=joo
koe $a

echo $numero # numerolla on arvo täälläkin vaikka se onkin määritelty local:iksi funktiossa: "koe"
#                      sillä se on funktiossa: "joo" tehty globaaliksi

***

Anonymous function on funktio jolla ei ole nimeä. Tarkastiottaen se ei ole totta, sillä anonymous funktiolla  on muuttujan nimi. Siis tässä tapauksessa tuo a - kaavassa: a=x**2+x-1
Koodia: [Valitse]
function koe () { apu=($@); for n in $(seq 1 $((${#@}-1))); do x=${apu[$n]}; echo $(( $1 )) ; done ;}; mat=(1 2 3);a=(x**2+x-1); koe $a ${mat[@]}

- tuon matriisin palautus sotkisi muutenkin hankalasti ymmärrettävää joten se jäi pois

***

Missään näissä desimaalimaematiikan laskuista ei ole käytetty bc:tä tai jotain muuta matematiikkaohjenlmaa sillä näillä pinillä hommilla ne hidastavat..

Neliöjuuri luvuista 1.0000001-99.999999. Välin ulkopuolisiin lukuihin pätee: "kerro tai jaa sadalla kunnes pääset tälle välille. Pilkku siirtyy aina yhdellä.". Desimaaleja tuloksessa on 8. Aikaa kuluu noin 2ms.
Koodia: [Valitse]
function neliöjuuri () {
juurrettava=${1//./}"000000000000000000"
juurrettava=${juurrettava:0:17}
y=2110000000
delta=1005000000
case ${1%%.*} in
    [1-9])    { for ((i=0;i<31;i++)); do
                  x=$(($y*$y))
                  (( $x>=$juurrettava )) && y=$(($y-$delta)) || y=$(($y+$delta))
                  delta=$(($delta/2))
                done ;} ;;
    [10-99]*) { for ((i=0;i<31;i++)); do
                x=$(($y*$y/10))
                 (( $x>=$juurrettava )) && y=$(($y-$delta)) || y=$(($y+$delta))
                 delta=$(($delta/2))
               done ;} ;;
    *) echo "kerro tai jaa sadalla kunnes pääset välille 1-100. Pilkku siirtyy aina yhdellä." ;;
esac
echo ${y:0:1}.${y:1} ;}

time neliöjuuri 2.43

***

BASH:in matematiikka toimii noin 50 mikrosekunnissa mutta se toimii vain kokonaisluvuilla. Mutta itseasiassa kaikki matematiikka tietokoneissa on kokonaislukumatematiikkaa, liukulukulaskut vain pitävät kirjaa mihin se desimaalipiste kuuluu. BASH:ista tämä kirjanpito puuttuu ja se on itse lisättävä. Ja koska kirjanpito on toteutettava korkeatason käskyillä hidastaa se melkolailla. Desimaalimatematiikan laskujen nopeus onkin BASH:issa luokkaa 0.2-0.5ms.

Ja näiden desimaalilaskujen numeromäärä sillon kun haetaan suurta nopeutta rajoittuu kokonaislukulaskujen 19 numeroon. Tämä on usein täysin epätyydyttävä merkkimäärä.

***

Desimaalimatematiikan peruskiviin kuuluu desimaalilukujen vertaaminen. Silloin kun joudutaan vertailemaan lukuja joissa saattaa olla yli 19 numeroa kannattaa käyttää tekstijono-vertailua sillä salahautojen väistelemisestä huolimatta se on yhtänopea kuin aritmeettinen desimaalivertain. Eikä merkkimäärällä ole rajoituksia tekstijonovertailussa. Myös kaikki lukujärjestelmät toimivat haxadesimaalit mukaanlaskien.

Tekstijonovertailuahan on käytetty aina sillä se on erinomainen numeroitakin vertailtaessa. Mutta noinvain käytettynä se höpertää monessa tilanteessa. Seuraavilla korjauksilla siitä tulee kelvollinen:

1. verrattavien lukujen etumerkit täytyy erottaa omiin muutujiinsa ja poistaa senjälkeen.
2. luvuissa täytyy aluksi olla desimaalipiste ja jos ei ole niin sellainen liitetään lukujen loppuun.
3. desimaaaliosista täytyy tehdä yhtäpitkät, elikä katkaista pitempi desimaaliosa lyhyemmän mittaiseksi.
4. lopulliset verrattavat ovat KokonaisosaDesimaaliosa; pelkkiä numeroita ilman desimaalipistettä
5. varsinainen suuruuden testaaminen täytyy suorittaa eritavoin eri etumerkkikombinaatioilla.

Ja näiden korjausten jälkeen se on hidas ellei sitä kirjoita oikein:
Koodia: [Valitse]
function onkoekasuurempi () { # rajoittamaton numeromäärä kokonais- ja desimaaliosissa
[[ ${1//[^-]/} ]] && m1=- || m1=+; [[ ${2//[^-]/} ]] && m2=- || m2=+
luku1=${1//[-+]/}; luku2=${2//[-+]/}    # etumerkki pois, myös +.
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1"." # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2"." # joten lisätään sellaien mikäli ei jo ole
desimaaliosa1=${luku1##*.}; desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}; desimaaliosa2pituus=${#desimaaliosa2}
kokonaisosa1=${1%%.*}; kokonaisosa2=${2%%.*}
(( ${#desimaaliosa2} <= ${#desimaaliosa1} )) && { apu=$desimaaliosa1; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1; luku2=$kokonaisosa2$desimaaliosa2
case $m1$m2 in 
+- )  echo 1 && return ;;
-+ )  echo 0 && return ;;
++ )  [[ $luku1 > $luku2 ]] && echo 1 || echo 0 && return ;;
-- )  [[ $luku1 > $luku2 ]] && echo 0 || echo 1 ;;
esac ;}

time onkoekasuurempi 11111.2 11111.19999 # kestää 1ms

***

Lukujen yhtäsuuruus neuvotaan toteamaan näin:

((10#$a == 10#$b)) && <tehdään jotakin>

Tuo 10# poistaa luvuista etunollat joten vertailu selviää "oktaaliansasta" mutta useimmat neuvojat jättävät tuon tarpeellisen osan neuvostaan pois. Jokatapauksessa esitetyllä tavalla yhtäsuuruutta testattaessa testattavien numeromäärä on rajoitettu siihen noin 19 numeroon joka on BASH:in matematiikassa suurin hyväksytty numero eikä se riitä alkuunkaan sillä tottakai BASH joutuu tekemisiin ulkomaailman kanssa ja sietähän tulee 64 numeroisia lukuja ja suurempiakin.

Mutta yhtäsuuruutta ei kannatakaan testata matematiikkamoottorissa vaan käyttää tekstijonovertailua sillä se on joustavinta ja nopeinta. Tekstijonovertailussa on lukujen yhtäsuuruuden vertailussa vain vähän asioita jotka voisivat viedä harhaan:
1. etunolla
2. + merkki
sillä kumpikaan ei vaikuta luvun numeeriseen arvoon mutta tekstijonoarvoon kyllä joten ne molemmat poistetaan. Mutta sensijaan kaikki lukujärjestelmät käyvät ja vertailtavissa saa olla mitähyvänsä vakiotekstiäkin. Desimaalipistekään ei ole ongelma.

Asiasta on viisainta tehdä funktio:
Koodia: [Valitse]
function yhtäsuuruustesti () {
luku1=${1//+/}; [[ ${luku1:0:1} == 0 ]] && luku1=${luku1:1} 
luku2=${2//+/}; [[ ${luku2:0:1} == 0 ]] && luku2=${luku2:1}
[[ $luku1 == $luku2 ]] && echo "arvoltaan luvut ovat samoja" || echo "luvut eivät ole samoja" ;}
# ja sen testaus:
a=1234567890123456789012345678901234567890.1234567890123456789012345678901234567890; b=+01234567890123456789012345678901234567890.1234567890123456789012345678901234567890;
yhtäsuuruustesti $a $b

***

Aikoinaan BASH:issa harrastetiin muotoiluohjelmia tulosteiden muotoilemiseen - mutta muotoiluohjelma kykenee muotoilemaan vain koko sivun eikä yksittäistä numeroa. Siitä on menty toiseen äärimmäisyyteen ja muotoillaan ainoastaan yksittäisiä numeroita ja tekstikenttiä ja oletetaan että sehän johtaa siihen että koko sivukin on muotoiltu.

Mutta numeron tulostamiseen ei ole keinoa joka ei joskus pursuisi yli ja silloin joko tulosteen ulkoasu tai numero kärsii. Tieteellinen esitysmuotokin on vain osaratkaisu. Printf tekee hyvää työtä, mutta jotkin asiat ovat sillekin liian vaikeita.

Esimerkiksi lukua tulostettaessa desimaalit on pakko katkaista. Mutta nitä ei saa raa-asti katkaista vaan pyöristää ottaen huomioon mitä numeroita on katkaisukohdan perässä.

printf pyöristäisikin muotoilemisen ohella. Mutta pyöristettäessä sen toiminta tökkii koska laskuissa on usein desimaalipisteenä piste ja Suomalaisen koodisivun desimaalipiste on pilkku. Senvuoksi kun yrittää pyöristää käyttäen printf-käskyä se välillä epäonnistuu. Printf:ää pitää käskeä omalla tavallaan sekä pisteelle että pilkulle.

Kunnolliseen muotoilemiseen tarvitaankin sekä numeroiden muotoilemista tulostettaessa että muotoiluohjelmaa - ja nuo jo unohdetut BASH:in muotoiluohjelmat kyllä toimivat edelleen. "paras" muotoiluohjelma on column -t ja se putkitetaan tarvittaessa tulostukseen. Eiväthän nuo muotoiluohjelmat nykyisiä vaatimuksia täytä - mutta silti niistä on joskus hyötyä. Muotoiluohjelman sijoitaminen oikeaan kohtaan ei ole aina helppoa.

Seuraava tulostusfunktio ei muuta tulostettavan luvun kokonaiosaa mutta pyöristää desimaaliosan niinkuin halutaan - ja sille kelpaa sekä piste että pilkku. Se on lisäksi nopeampikin kuin printf: 
Koodia: [Valitse]
function round_in_decimals () { (( $# == 2 )) && decimals=$2 || decimals=0; kokonaisosa=${1%%[,.]*}; desimaaliosa=${1##*[,.]}; (( $decimals == 0 )) && echo $(($kokonaisosa+$((${desimaaliosa:0} >= 50)))) && return; desimaaliosa=$desimaaliosa"0000000000000000000000";echo $kokonaisosa.$(( ${desimaaliosa:0:$decimals}+$(( ${desimaaliosa:$decimals+1} >= 50)) )) ;}
# kutsu:
round_in_decimals 1.51515773 2  # tulostaa 1.52

# lisätestaaminen:
round_in_decimals .5   # tämän pitää tulostaa 0 # parametria ei tarvitse kirjoittaa kun sen arvo on 0
round_in_decimals .51  # tämän pitää tulostaa 1
round_in_decimals 1.51 # tämän pitää tulostaa 2

Muut viritelmät lukujen tulostamiseksi alkavat nopeudeltaa yleensä 4ms nurkilta - mutta ei voi ihan yksiselitteisesti tuomita niitä huonoiksi sillä esimerkiksi sovellukset jotka käyttävät bc:tä voivat samalla tehdä matemaattisia ihmetekoja.

***

Se että tuo 6-rivinen funktio jossa on monimutkaista toimintaakin voi olla nopeampi kuin prinf-käsky sai jälleen kerran ajattelemaan legendaa BASH:in hitaudesta.

Nopeudessa taitaa olla kysymys seuraavasta: BASH:issa on nopeudeltaan kahdentyyppisiä käskyjä - siitä toki manuaaleissa ohimennen kerrotaan muttei sitä että niiden nopeudet eroavat monikymmenkertaisesti:
 
1. sellaisia käskyjä joiden koodi on BASH:iin liitetty. Tällaiset käskyt ovat tosinopeita - no nopeita BASH-yksiköissä. Näitä saa olla viitisenkymmentäkin peräkkäin ennenkuin suoritus kestää millisekunnin.

2. sellaisia käskyjä jotka täytyy tulkita ennen linkittämistä. Nämä ovat niitä hitaita joista useimmat kestävät luokkaa millisekunnin.

Noissa nopeissa käskyissä on paljon vaikeasti muistettavaa digisotkua jonka kirjoittaminen vaatii lisäksi näppäin-akrobatiaa - ja koska noiden käskyjen nopeudesta ei puhuta niin niitä ei opetella käyttämään vaan totutaan käyttämään yksinomaan helppoja ja hitaita ulkoisia käskyjä - jotka lisäksi kaipaavat apua sed:iltä ja awk:ilta jotka ovat pikkuhommissa vielä hitaampia.

Koskei ole täysin tajuttu että nopeitakin käskyjä löytyy niin on luultu ettei BASH:ista ole ollenkaan numeronmurskaamiseen ilman bc:n apua. Tätä on vahvistanut luulo ettei BASH kykene desimaalilaskentaan. Lisäksi luullaan että matematiikan 19:sta merkkin lukumäärää ei voisi mitenkään lisätä. Mutta pienissä "nelilaskin" laskuissa desimaaliluvuillakin BASH on neljäkertaa nopeampi kuin bc.

BASH:in vastustajia täytyy ihailla: väitetään väärin että BASH ei osaa palauttaa funktioparametreja, eval-käskyn tuomitseminen - mutta vain BASH:issa, kirjasto-osoittimen poistaminen, väärät väitteet ensimmäisenluokan funktioita ja niinpoispäin. Mikähyvänsä näistä riittää tuhoamaan melkein mitavaan. Tuntuu siltä että BASH halutaan tuhota ja nimenomaan niin etteise koskaan palaisi.   

Sehän selvisi jo aikoinaan että sed, awk ja bc täytyy unohtaa kun halutaan nopeutta yksinkertaisiin tehtäviin. Mutta se täytyi yrittää selvittää mitä kaikkea muuta kuin BASH:in nopeita käskyjä voi nopeaan skriptiin ottaa mukaan: ilmeisesti ainakin funktiot ja looppit ovat nopeita - täytyy korjata pahaa puhettani looppien hitaudesta: eivät loopit ole hitaita vaan se minkä ne suorittavat.

Käytännön skripteistä näkee parhaiten mikä kannattaa:

75
BASH:ista haastellaan potaskaa kaikkialla: osoitus siitä että BASH hallitsee ohjelmarakenteen nimeltään: first-class function
Koodia: [Valitse]
function koe () { local numero; $1 $2 numero; echo $numero ;}   
function joo () { readarray $1 < <(echo 12345) ;}
a=joo
koe $a

echo $numero # numerolla on arvo täälläkin vaikka se onkin määritelty local:iksi funktiossa: "koe"
#                      sillä se on funktiossa: "joo" tehty globaaliksi

***

Anonymous function on funktio jolla ei ole nimeä. Tarkastiottaen se ei ole totta, sillä anonymous funktiolla  on muuttujan nimi. Siis tässä tapauksessa tuo a - kaavassa: a=x**2+x-1
Koodia: [Valitse]
function koe () { apu=($@); for n in $(seq 1 $((${#@}-1))); do x=${apu[$n]}; echo $(( $1 )) ; done ;}; mat=(1 2 3);a=(x**2+x-1); koe $a ${mat[@]}

- tuon matriisin palautus sotkisi muutenkin hankalasti ymmärrettävää joten se jäi pois

***

Missään näissä desimaalimaematiikan laskuista ei ole käytetty bc:tä tai jotain muuta matematiikkaohjenlmaa sillä näillä pinillä hommilla ne hidastavat..

Neliöjuuri luvuista 1.0000001-99.999999. Välin ulkopuolisiin lukuihin pätee: "kerro tai jaa sadalla kunnes pääset tälle välille. Pilkku siirtyy aina yhdellä.". Desimaaleja tuloksessa on 8. Aikaa kuluu noin 2ms.
Koodia: [Valitse]
function neliöjuuri () {
juurrettava=${1//./}"000000000000000000"
juurrettava=${juurrettava:0:17}
y=2110000000
delta=1005000000
case ${1%%.*} in
    [1-9])    { for ((i=0;i<31;i++)); do
                  x=$(($y*$y))
                  (( $x>=$juurrettava )) && y=$(($y-$delta)) || y=$(($y+$delta))
                  delta=$(($delta/2))
                done ;} ;;
    [10-99]*) { for ((i=0;i<31;i++)); do
                x=$(($y*$y/10))
                 (( $x>=$juurrettava )) && y=$(($y-$delta)) || y=$(($y+$delta))
                 delta=$(($delta/2))
               done ;} ;;
    *) echo "kerro tai jaa sadalla kunnes pääset välille 1-100. Pilkku siirtyy aina yhdellä." ;;
esac
echo ${y:0:1}.${y:1} ;}

time neliöjuuri 2.43

***

BASH:in matematiikka toimii noin 50 mikrosekunnissa mutta se toimii vain kokonaisluvuilla. Mutta itseasiassa kaikki matematiikka tietokoneissa on kokonaislukumatematiikkaa, liukulukulaskut vain pitävät kirjaa mihin desimaalipiste kuuluu - mutta tämä kirjanpitokin voidaan tehdä joko vaikeasti tai helposti,  ja se helpompitapa on että käsiteltävien desimaaliosat muodostetaan yhtäpitkiksi lisäämällä lyhyempään desimaaliosaan riitävästi peränollia. BASH:ista tämä kirjanpito puuttuu ja se on itse lisättävä. Ja koska kirjanpito on toteutettava korkeatason käskyillä hidastaa se melkolailla. Desimaalimatematiikan laskujen nopeus onkin BASH:issa luokkaa 0.2-0.5ms.

Ja näiden desimaalilaskujen numeromäärä sillon kun haetaan suurta nopeutta rajoittuu kokonaislukulaskujen 19 numeroon. Tämä on usein täysin epätyydyttävä merkkimäärä.

***

Desimaalimatematiikan peruskiviin kuuluu desimaalilukujen vertaaminen. Silloin kun joudutaan vertailemaan lukuja joissa saattaa olla yli 19 numeroa kannattaa käyttää tekstijono-vertailua sillä salahautojen väistelemisestä huolimatta se on yhtänopea kuin aritmeettinen desimaalivertain. Eikä merkkimäärällä ole rajoituksia tekstijonovertailussa. Myös kaikki lukujärjestelmät toimivat haxadesimaalit mukaanlaskien.

Tekstijonovertailuahan on käytetty aina sillä se on erinomainen numeroitakin vertailtaessa. Mutta noinvain käytettynä se höpertää monessa tilanteessa. Seuraavilla korjauksilla siitä tulee kelvollinen:

1. verrattavien lukujen etumerkit täytyy erottaa omiin muutujiinsa ja poistaa senjälkeen.
2. luvuissa täytyy aluksi olla desimaalipiste ja jos ei ole niin sellainen liitetään lukujen loppuun.
3. desimaaaliosista täytyy tehdä yhtäpitkät, elikä katkaista pitempi desimaaliosa lyhyemmän mittaiseksi.
4. lopulliset verrattavat ovat KokonaisosaDesimaaliosa; pelkkiä numeroita ilman desimaalipistettä
5. varsinainen suuruuden testaaminen täytyy suorittaa eritavoin eri etumerkkikombinaatioilla.

Ja näiden korjausten jälkeen se on hidas ellei sitä kirjoita oikein:
Koodia: [Valitse]
function onkoekasuurempi () { # rajoittamaton numeromäärä kokonais- ja desimaaliosissa
[[ ${1//[^-]/} ]] && m1=- || m1=+; [[ ${2//[^-]/} ]] && m2=- || m2=+
luku1=${1//[-+]/}; luku2=${2//[-+]/}    # etumerkki pois, myös +.
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1"." # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2"." # joten lisätään sellaien mikäli ei jo ole
desimaaliosa1=${luku1##*.}; desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}; desimaaliosa2pituus=${#desimaaliosa2}
kokonaisosa1=${1%%.*}; kokonaisosa2=${2%%.*}
(( ${#desimaaliosa2} <= ${#desimaaliosa1} )) && { apu=$desimaaliosa1; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1; luku2=$kokonaisosa2$desimaaliosa2
case $m1$m2 in 
+- )  echo 1 && return ;;
-+ )  echo 0 && return ;;
++ )  [[ $luku1 > $luku2 ]] && echo 1 || echo 0 && return ;;
-- )  [[ $luku1 > $luku2 ]] && echo 0 || echo 1 ;;
esac ;}

time onkoekasuurempi 11111.2 11111.19999 # kestää 1ms

***

Lukujen yhtäsuuruus neuvotaan toteamaan näin:

((10#$a == 10#$b)) && <tehdään jotakin>

Tuo 10# poistaa luvuista etunollat joten vertailu selviää "oktaaliansasta" mutta useimmat neuvojat jättävät tuon tarpeellisen osan neuvostaan pois. Jokatapauksessa esitetyllä tavalla yhtäsuuruutta testattaessa testattavien numeromäärä on rajoitettu siihen noin 19 numeroon joka on BASH:in matematiikassa suurin hyväksytty numero eikä se riitä alkuunkaan sillä tottakai BASH joutuu tekemisiin ulkomaailman kanssa ja sietähän tulee 64 numeroisia lukuja ja suurempiakin.

Mutta yhtäsuuruutta ei kannatakaan testata matematiikkamoottorissa vaan käyttää tekstijonovertailua sillä se on joustavinta ja nopeinta. Tekstijonovertailussa on lukujen yhtäsuuruuden vertailussa vain vähän asioita jotka voisivat viedä harhaan:
1. etunolla
2. + merkki
sillä kumpikaan ei vaikuta luvun numeeriseen arvoon mutta tekstijonoarvoon kyllä joten ne molemmat poistetaan. Mutta sensijaan kaikki lukujärjestelmät käyvät ja vertailtavissa saa olla mitähyvänsä vakiotekstiäkin.

Asiasta on viisainta tehdä funktio:
Koodia: [Valitse]
function yhtäsuuruustesti () {
luku1=${1//+/}; [[ ${luku1:0:1} == 0 ]] && luku1=${luku1:1} 
luku2=${2//+/}; [[ ${luku2:0:1} == 0 ]] && luku2=${luku2:1}
[[ $luku1 == $luku2 ]] && echo "arvoltaan luvut ovat samoja" || echo "luvut eivät ole samoja" ;}
# ja sen testaus:
a=1234567890123456789012345678901234567890.1234567890123456789012345678901234567890; b=+01234567890123456789012345678901234567890.1234567890123456789012345678901234567890;
yhtäsuuruustesti $a $b

***

Aikoinaan BASH:issa harrastetiin muotoiluohjelmia tulosteiden muotoilemiseen - mutta muotoiluohjelma kykenee muotoilemaan vain koko sivun eikä yksittäistä numeroa. Siitä on menty toiseen äärimmäisyyteen ja muotoillaan ainoastaan yksittäisiä numeroita ja tekstikenttiä ja oletetaan että sehän johtaa siihen että koko sivukin on muotoiltu.

Mutta numeron tulostamiseen ei ole keinoa joka ei joskus pursuisi yli ja silloin joko tulosteen ulkoasu tai numero kärsii. Tieteellinen esitysmuotokin on vain osaratkaisu. Printf tekee hyvää työtä, mutta jotkin asiat ovat sillekin liian vaikeita.

Esimerkiksi lukua tulostettaessa desimaalit on pakko katkaista. Mutta nitä ei saa raa-asti katkaista vaan pyöristää ottaen huomioon mitä numeroita on katkaisukohdan perässä.

printf pyöristäisikin muotoilemisen ohella. Mutta pyöristettäessä sen toiminta tökkii koska laskuissa on usein desimaalipisteenä piste ja Suomalaisen koodisivun desimaalipiste on pilkku. Senvuoksi kun yrittää pyöristää käyttäen printf-käskyä se välillä epäonnistuu. Printf:ää pitää käskeä omalla tavallaan sekä pisteelle että pilkulle.

Kunnolliseen muotoilemiseen tarvitaankin sekä numeroiden muotoilemista tulostettaessa että muotoiluohjelmaa - ja nuo jo unohdetut BASH:in muotoiluohjelmat kyllä toimivat edelleen. "paras" muotoiluohjelma on column -t ja se putkitetaan tarvittaessa tulostukseen. Eiväthän nuo muotoiluohjelmat nykyisiä vaatimuksia täytä - mutta silti niistä on joskus hyötyä. Muotoiluohjelman sijoitaminen oikeaan kohtaan ei ole aina helppoa.

Seuraava tulostusfunktio ei muuta tulostettavan luvun kokonaiosaa mutta pyöristää desimaaliosan niinkuin halutaan - ja sille kelpaa sekä piste että pilkku. Se on lisäksi nopeampikin kuin printf: 
Koodia: [Valitse]
function round_in_decimals () { (( $# == 2 )) && decimals=$2 || decimals=0; kokonaisosa=${1%%[,.]*}; desimaaliosa=${1##*[,.]}; (( $decimals == 0 )) && echo $(($kokonaisosa+$((${desimaaliosa:0} >= 50)))) && return; desimaaliosa=$desimaaliosa"0000000000000000000000";echo $kokonaisosa.$(( ${desimaaliosa:0:$decimals}+$(( ${desimaaliosa:$decimals+1} >= 50)) )) ;}
# kutsu:
round_in_decimals 1.51515773 2  # tulostaa 1.52

# lisätestaaminen:
round_in_decimals .5   # tämän pitää tulostaa 0 # parametria ei tarvitse kirjoittaa kun sen arvo on 0
round_in_decimals .51  # tämän pitää tulostaa 1
round_in_decimals 1.51 # tämän pitää tulostaa 2

Muut viritelmät lukujen tulostamiseksi alkavat nopeudeltaa yleensä 4ms nurkilta - mutta ei voi ihan yksiselitteisesti tuomita niitä huonoiksi sillä esimerkiksi sovellukset jotka käyttävät bc:tä voivat samalla tehdä matemaattisia ihmetekoja.

***

Se että tuo 6-rivinen funktio jossa on monimutkaista toimintaakin voi olla nopeampi kuin prinf-käsky sai jälleen kerran ajattelemaan legendaa BASH:in hitaudesta.

Nopeudessa taitaa olla kysymys seuraavasta: BASH:issa on nopeudeltaan kahdentyyppisiä käskyjä - siitä toki manuaaleissa ohimennen kerrotaan muttei sitä että niiden nopeudet eroavat monikymmenkertaisesti:
 
1. sellaisia käskyjä joiden koodi on BASH:iin liitetty. Tällaiset käskyt ovat tosinopeita - no nopeita BASH-yksiköissä. Näitä saa olla viitisenkymmentäkin peräkkäin ennenkuin suoritus kestää millisekunnin.

2. sellaisia käskyjä jotka täytyy tulkita ennen linkittämistä. Nämä ovat niitä hitaita joista useimmat kestävät luokkaa millisekunnin.

Noissa nopeissa käskyissä on paljon vaikeasti muistettavaa digisotkua jonka kirjoittaminen vaatii lisäksi näppäin-akrobatiaa - ja koska noiden käskyjen nopeudesta ei puhuta niin niitä ei opetella käyttämään vaan totutaan käyttämään yksinomaan helppoja ja hitaita ulkoisia käskyjä - jotka lisäksi kaipaavat apua sed:iltä ja awk:ilta jotka ovat pikkuhommissa vielä hitaampia.

Koskei ole täysin tajuttu että nopeitakin käskyjä löytyy niin on luultu ettei BASH:ista ole ollenkaan numeronmurskaamiseen ilman bc:n apua. Tätä on vahvistanut luulo ettei BASH kykene desimaalilaskentaan. Lisäksi luullaan että matematiikan 19:sta merkkin lukumäärää ei voisi mitenkään lisätä. Mutta pienissä "nelilaskin" laskuissa desimaaliluvuillakin BASH on neljäkertaa nopeampi kuin bc.

BASH:in vadtustajia täytyy ihailla: väitetään hieman väärin että BASH ei osaa palauttaa funktioparametreja, eval-käskyn tuomitseminen - mutta vain BASH:issa, kirjasto-osoittimen poistaminen, väärät väitteet ensimmäisenluokan funktioita ja niinpoispäin. Mikähyvänsä näistä riittää tuhoamaan melkein mitavaan. BASH halutaan tuhota.   

Toki BASH:issa on paljon ihan oikeaakin kritisoitavaa. Ja jotakin täytyisi tehdä jotta sen kritisoitavan voisi skripteissään tehdä oikealla tavalla - esimerkiksi palauttaa kirjasto-osoitin ja koota muutama kirjasto.

Sehän selvisi jo aikoinaan että sed, awk ja bc täytyy unohtaa kun halutaan nopeutta yksinkertaisiin tehtäviin. Mutta se täytyi yrittää selvittää mitä kaikkea muuta kuin BASH:in nopeita käskyjä voi nopeaan skriptiin ottaa mukaan: ilmeisesti ainakin funktiot ja looppit ovat nopeita - täytyy korjata pahaa puhettani looppien hitaudesta: eivät loopit ole hitaita vaan se minkä ne suorittavat.

Käytännön skripteistä näkee parhaiten mikä kannattaa:

Ensin rajoittamattoman monen rajoittamattoman moninumeroisten lukujen yhteenlaskin jossa on pari funktiota ja neljä looppia. Tätä ei ole suoranaisesti tarkoitettu käytettäväksi vaan se on tehty nopeiden käskyjen löytämiseksi. Lopussa kuvatulla laskulla skriptin läpikäyminen vaatii noin 150:n monipuolisen käskyn suorittamisen elikä noin 20 mikrosekuntia per käsky -> siis kaikki sen käskyt ovat nopeita.
Koodia: [Valitse]
function radd () { 

function desimaalisumma () { for((j=1;j<=$2;j++)); do summa=$((summa+${desimaaliosa[j]:$1:1})); done; [[ $muistinumero ]] || muistinumero=0;summa=$((summa+$muistinumero)); muistinumero=0 ;}

function kokonaissumma  () { for((j=1;j<=$2;j++)); do summa=$((summa+${kokonaisosa[j]:$1:1})); done; [[ $muistinumero ]] || muistinumero=0; summa=$((summa+$muistinumero)); muistinumero=0 ;}

i=1; maxdesimaalit=0; maxkokonaiset=0; muistinumero=0; for n in $@; do desimaaliosa[i]=${n##*.}; desimaaliosanpituus[i]=${#desimaaliosa[i]}; (( ${desimaaliosanpituus[i]} >= $maxdesimaalit )) && maxdesimaalit=${desimaaliosanpituus[i]}; kokonaisosa[i]=${n%%.*};  kokonaisosanpituus[i]=${#kokonaisosa[i]}; (( ${kokonaisosanpituus[i]} >= $maxkokonaiset )) && maxkokonaiset=${kokonaisosanpituus[i]}; (( i++ )); done

i=1; for n in $@; do desimaaliosa[i]=${n##*.}"000000000000000000"; desimaaliosa[i]=${desimaaliosa[i]:0:$maxdesimaalit};
kokonaisosa[i]="000000000000"${kokonaisosa[i]}; kokonaisosa[i]=${kokonaisosa[i]: -$maxkokonaiset};
echo ${kokonaisosa[i]}.${desimaaliosa[i]}; (( i++ )); done 

desimaalit=''; for((i=maxdesimaalit-1;i>=0;i--)); do summa=0; desimaalisumma i $# ; desimaalit=${summa: -1}$desimaalit; muistinumero=${summa:0: -1}; done; # echo $desimaalit' '$muistinumero; read

kokonaiset=''; for((i=maxkokonaiset-1;i>=0;i--)); do summa=0; kokonaissumma i $# ; kokonaiset=${summa: -1}$kokonaiset; muistinumero=${summa:0: -1}; done; echo $muistinumero$kokonaiset.$desimaalit
}

time radd 1.17 22.222 3.33 4.4





76
Desimaaliluvun neliöjuuren määritys pelkästään BASH:in peruskäskyillä.

Mikäli x:n neliöjuuri on y niin silloin: y*y=x. Tätä tosiasiaa soveltaen voi tehdä binäärihaun neliöjuuren selvittämiseksi.
- tämän ohjelman pääasiallisin tarkoitus ei ole neliöjuuren etsiminen vaan desimaalilaskennan toimimisen testaaminen: jos yksikin palikka höpertää edes joskus niin metsään mennään. Silti binäärilaskentakin on merkityksellinen.

Binäärihaussa tässätapauksessa tarvittavat ohjelmat - mutta kaikissa toteutuksissa ne ovat samantapaisia:
y*y (luvun kertominen itsellään)
kahdellajako
yhteenlasku
vähennyslasku ( voidan käyttää yhteenlaskua kun yhteenlaskettavan eteen laitetaan miinusmerkki)
lukujenvertailu       
binäärihaun ohjaus.

- desimaalipisteiden kuljettaminen mukana kuluttaa paljon aikaa koskei Bash desimaalipistettä edes tunne. Mutta tässävaiheessa desimaalipisteiden mukanaolo tekee toiminnasta käsitettävämmän. Ainoastaan desimaaliluvuista väliltä 2-6 saa neliöjuuren käytettyjen toteutuksien rajoitteiden takia. Näidenkin lukujen neliöjuurissa on vain noin 8 desimaalia ja aikaakin kuluu luokkaa 80ms johtuen osittain siitä että toiminta vaatii noin 2100:n lauseen suorittamisen. Desimaalilaskennassakin on loputtomasti parannettavaa nopeuden, kykyjen ja koodin koon suhteen.  Toiminnankuvaus:

1. Muodostetaan alkuarvot: y=x/2 ja delta=x/4. Haun alkuarvoksi otetaan y.   
2. aloitetaan esimerkiksi 30:een hakuun rajoitettu binäärihaku. Teoriassa lopputulos tarkentuu 3:lla desimaalilla jokaista kymmentä binäärihakua kohden.
3. lasketaan y*y
4. mikäli y:ssä desimaalipisteen kanssa on yli 11 merkkiä niin tulostetaan y ja lopetetaan. Tätä kohtaa ei välttämättä kannata toteuttaa.
5. verrataan tulosta x:ään ja vertailun perusteella lasketaan uusi y kaavalla: y=y+delta tai y=y-delta
6. delta jaetaan kahdella ja palataan binäärihaun alkuun.
7. kun binäärihaku on loppunut niin tulostetaan y ja lopetetaan.

Koodia: [Valitse]
function y*y () {
luku1=$1
luku2=$1
[[ ${luku1//[^.]/} ]] || luku1=$luku1"."
[[ ${luku2//[^.]/} ]] || luku2=$luku2"."
desimaaliosa1=${luku1##*.}
desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}
desimaaliosa2pituus=${#desimaaliosa2}
desimaalejakaikkiaan=$(($desimaaliosa1pituus+$desimaaliosa2pituus ))
tulo=00000000000$((10#${luku1//./}*10#${luku2//./}))    # mikäli kokonaisia ei ole ja desimaaliosan alussa on nollia katoavat ne tässä laskussa joten lisätään tuloksen eteen paljon nollia.
luku=${tulo:0: -$desimaalejakaikkiaan}                  # etunollia otetaan lopputulokseen kumminkin vain niin monta kuin niitä on kadonnut. Siis yleensä ei yhtään.
echo $((10#$luku)).${tulo: -$desimaalejakaikkiaan:8} ;} # $((10#$luku)) poistaa kokonaisosasta etunollat


function lukujenvertailu () {
luku1=$1
luku2=$2
[[ ${luku1//[^.]/} ]] || luku1=$luku1".0"
[[ ${luku2//[^.]/} ]] || luku2=$luku2".0"
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}
desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1
luku2=$kokonaisosa2$desimaaliosa2
(( $luku1 >= $luku2 )) && echo 0 || echo 1 ;} 


function kahdellajako () {
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
luku2=2.0
desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}
luku1ilmandesimaalipistetta=$kokonaisosa1$desimaaliosa1 #; echo $desimaaliosa1
luku2ilmandesimaalipistetta=$kokonaisosa2$desimaaliosa2 #; echo $desimaaliosa2
kokonaiset=$((10#$luku1ilmandesimaalipistetta/10#$luku2ilmandesimaalipistetta))
jakojaannos=$((10#$luku1ilmandesimaalipistetta%10#$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=000000000000000$desimaalit
echo $kokonaiset.${desimaalit: -9} ;}


function yhteenlasku () { # voi käyttää vähennyslaskuunkin
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
luku1=$1
luku2=$2
luku1=${luku1//[-+]/}; luku2=${luku2//[-+]/}
luku1=${luku1//-./-0.}; luku2=${luku2//-./-0.}
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellaien mikäli ei jo ole
desimaaliosa1=${luku1##*.}; desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}; desimaaliosa2pituus=${#desimaaliosa2}
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo a$desimaaliosa2' 'b$desimaaliosa1 ; read # testatessa tämä on tarpeen
desimaaleja=${#desimaaliosa1} #; echo $desimaaleja
kokonaisluku1=${luku1%%.*}
kokonaisluku2=${luku2%%.*}
kokoluku1=$kokonaisluku1$desimaaliosa1 #; echo $kokoluku1
kokoluku2=$kokonaisluku2$desimaaliosa2 #; echo $kokoluku2
(( $m2 +1 )) && luku=$((10#$kokoluku1+10#$kokoluku2)) || luku=$((10#$kokoluku1-10#$kokoluku2))
echo ${luku:0: -$desimaaleja}.${luku: -$desimaaleja} ;}


time { juurrettava=3 # tämä on binäärihakua ohjaava pääohjelma
y=$(kahdellajako $juurrettava)
delta=$(kahdellajako $y)
for ((i=0;i<30;i++)); do
  x=$( y*y $y)
  (( $(lukujenvertailu $juurrettava $x) )) && y=$(yhteenlasku $y -$delta) || y=$(yhteenlasku $y $delta)
  delta=$(kahdellajako $delta)
done
echo $y ;}

***

Virtuoositkin haluavat korvauksen ponnisteluistaan eivätkä siten kerro saavutuksistaan julkisesti. Mutta eivät malta olla vihjailematta.

Esimerkiksi aikoinaan luin erään virtuoosin kirjoituksia ja niissä oli ohje: ei pidä käyttää sed:iä tai awk:ia ellei se ole ihan välttämätöntä sillä ne hidastavat erittäin paljon. Ihmettelin kirjoitusta silloin sillä sed ja awk ne vasta nopeita ovatkin. Kuinka päteväntuntuinen virtuoosi voi tuommoisia kirjoittaa?

Mutta desimaalilaskenta opetti että virtuoosi puhui aivan totta sillä yksikin awk tai sed laskee nopeuden noin kymmenenteen osaan - kyse on siitä muutaman millisekunnin ajasta joka kuluu sed:iä tai awk:ia kutsuttaessa ja jos niille antaa vain jonkun pienen tehtävän niin BASH itse olisi suorittanut tehtävän paljon nopeammin.

Esimerkiksi BASH:ista löytyy käsky sille kuinka sana vaihdetaan toiseen elikä siihen hommaan johon sed:iä melkein aina käytetään - tosin BASH:in käsky tunnistaa vain yksinkertaiset regex:ät. Samoin moniin awk:in yksinkertaisiin tehtäviin löytyy BASH:ista vastaava toiminta.

Kuljenko siis desimaalilaskennassakin toisten jalanjäljillä?

*** 

Uudet versiot desimaalilukujen tuplaamiseen ja puolittamiseen - ja niille yksi testaus:
Koodia: [Valitse]

function tuplaa () { [[ ${1//[^.]/} ]] && luku=$1"0"|| luku=$1".0"; desimaaliosa=${luku##*.}; desimaaliosanpituus=${#desimaaliosa}; luku=${luku//./}; let "luku <<=1";  apu=${luku: -$desimaaliosanpituus:$desimaaliosanpituus-1}; (( $apu )) && echo ${luku:0: -$desimaaliosanpituus}.$apu || echo ${luku:0: -$desimaaliosanpituus} ;}

function puolita () { [[ ${1//[^.]/} ]] && luku=$1"0"|| luku=$1".0"; desimaaliosa=${luku##*.}; desimaaliosanpituus=${#desimaaliosa}; luku=${luku//./}; let "luku >>=1"; echo ${luku:0: -$desimaaliosanpituus}.${luku: -$desimaaliosanpituus:desimaaliosanpituus-$((${luku: -1} == 0)) } ;} 

# Tässä se testaus:
puolita $(tuplaa 555.555) # arvo 555.555 ja sen tupla elikä 1111.110 ovat muunnoksille haasteellisia. Tarkoituksena on myös selvittää tuleeko ulos sama luku kuin meni sisään.

***

Koskei BASH desimaalilukuja tunne niin desimaalilaskenta suoritetaan BASH:in matematiikkaoperaatioilla tuo desimaalipiste poistettuna. Siitä seuraa nopeus mutta myös se että luvuissa saa olla korkeintaan 19 numeroa laskematta mukaan tuota desimaalipistettä - myös laskujen tuloksen pitää olla 19 numeroa pienempi.

Desimaalilaskennan skriptit ovatkin liian nopeita jotta niiden suoritusajan saisi mitattua. Esimerkiksi uusien versioiden desimaaliluvun tuplaamiseen tai puolittamiseen pitäisi olla nopeampia kuin edellisten versioiden. Mutta kuinka todeta se selvin numeroin? Nimittäin time-käsky antaa nopeudeksi yleensä nollan ja harvemmin 0.001 sekuntia. Keskiarvo monesta mitauksesta on arviolta jotain .2ms - varsin epämääräistä. Tarkempi arvo saadaan näin:

- ensin mitataan pohja-aika: time { for n in {1..1000}; do : ; read -t .01 ; done ;}
joka on noin 10.300
- sitten mitataan ohjelman suoritusaika: time { for n in {1..1000}; do tuplaa 1 ; read -t .01 ; done ;}
joka on noin 10.750. Joten kokonaisaika on noin 0.450ms
- kokonaisaika on: (aika2-aika1)/1000
- onko aika sittenkään yhtään tarkempi on vain arvattavissa - ehkä se on moninumeroista potaskaa.
- käsky: read -t 1 on tarkka 1 sekunnin viive. Tarkkuus lienee mikrosekunteja.
- käsky : on käsky olla tekemättä mitään joten ei se oikein mitään kestäkään.
- ilmeisesti enin osa ajasta matematiikalaskennan skripteissä kuluu tulkin kutsumiseen sillä sillä ei tunnu olevan suurtakaan väliä montako lausetta niissä on. Siis ilmeisesti isonkin ohjelman suorittaminen olisi nopeaa jos sen lauseet kirjoittaa tulkille mieleisiksi.

tässä yhteydessä on syytä kertoa mitä time-käskyn real, user ja system ajat ovat:
- real on ohjelman käyttäjätilassa (user) viettämä aika ja siinä on lisänä kaikki ne ajat jotka prosessi joutuu odottamaan isokenkäisempien prosessien tunkiessa väliin. Tämä on se aika jonka käyttäjän kannalta on kuluu.
- user on se aika joka kuluu käyttäjätilassa työskentelyyn laskematta mukaan odotusaikoja.
- system on se aika joka kuluu systeemipalveluissa.
- ja kun soppaan lisätään linuxin kyky multiprosessointiin (system-tilassa) niin varsinkin nopeilla skripteillä user+system voi olla suurempi kuin real.

- oikeintehdyllä skriptillä real-aika on mahdollisimman pieni, user-aika siitä vain kotuullisesti pienempi ja system-aikakin jotain vähän suurempaa kuin 0. Eri suorituskerroilla arvot saattavat vaihdella paljonkin.

- käyttöjärjestelmä myöntää jokaiselle prosessille aikaviipaleita ja päättää myös koska niitä myönnetään. Käyttöjärjestelmä voi kyllä keskeyttää jo alkaneen prosessin mutta niin ei tapahdu usein vaan odotusajat ovat niitä aikoja kun toiset tunkee väliin. Jos siis aika on tarpeeksi lyhyt niin siihen tuskin kukaan kerkiää väliin ja silloin real-aika on pieni ja user-aika siitä vain vähän pienempi.

*** 

Kertolasku jossa sekä kertoja että kerrottava voivat olla 16-numeroisia kokonaislukuja ja tulos 32 numeroinen.
 
Desimaalilaskenta on nopeaa niinkauan kuin tyydytään toimimaan alle 19-numeroisilla luvuilla. Myös kaksinkertaisen numeromäärän desimaalisummain on nopea. Mutta "kaksinkertaisen numeromäärän" kertolasku kestää 8ms sillä sen täytyy käyttää neljää normaalia kertolaskua ja kolmea kaksinkertaisen numeromäärän yhteenlaskua.

Ei etumerkin käsittelyä eikä desimaalipistettä ole toteutettu vaikka se ei olisi vaikeaa. Sillä tämä on liian hidas yleisempään käyttöön mutta sopii desimaalilaskennan testaamiseen.
Koodia: [Valitse]
function summaa36 () { # kokonaislukujen summain kaksinkertaisella numeromäärällä
luku1pituus=${#1}
luku2pituus=${#2}
luku1=00000000000000000000000000000000000000$1
luku2=00000000000000000000000000000000000000$2
luku1=${luku1: -36} # echo $luku1
luku2=${luku2: -36} # echo $luku2
luku11=${luku1:0:18}
luku12=${luku1:18}; luku12=${luku11//[^-]/}$luku12 # echo a$luku11' 'b$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; luku22=${luku21//[^-]/}$luku22 # echo c$luku21' 'd$luku22
tokaluku=$((10#$luku12+10#$luku22))  # Kun käy niin että tokaluku vuotaa yli lisätään ekalukuun 1 ja #leikataan tokaluvusta ensimmäinen merkki pois (se on se ylivuotonumero ja sen arvo on silloin 1)
((10#${#tokaluku} > 10#${#luku12} || 10#${#tokaluku} > 10#${#luku22} )) && ylivuoto=1 || ylivuoto=0
(( $ylivuoto )) && tokaluku=${tokaluku:1} # echo $ylivuoto
[[ ${luku11//[^-]/} == - && ${luku21//[^-]/} == - ]] && ekaluku=$((10#$luku11+10#$luku21-$ylivuoto)) ||ekaluku=$((10#$luku11+10#$luku21+$ylivuoto))
(( $ekaluku )) && echo $ekaluku$tokaluku || echo $tokaluku ;}


function kerro16 () { # kertolasku kun jäsenet ovat 16 numeroisia kokonaislukuja.
luku1=000000000000000000$1
luku2=000000000000000000$2
a=${luku1: -16:8}; b=${luku1: -8}
c=${luku2: -16:8}; d=${luku2: -8} # echo $a' '$b; read # echo $c' '$d
ala=$((10#$b*10#$d))
keski1=$((10#$d*10#$a))"00000000"
keski2=$((10#$c*10#$b))"00000000"
yla=$((10#$a*10#$c))"0000000000000000"
summa=$(summaa36 $ala $keski1)
summa=$(summaa36 $summa $keski2)
summa=$(summaa36 $summa $yla)
echo $summa ;}

kerro16 1111111122222222 3333333344444444  # tulokseksi pitää tulla 3703703753086418641975301234568

***

Rekursio on ohjelmointirakenne jossa funktio kutsuu itseään.

BASH:issa rekursio on hidas joten sitä ei kannata käyttää kuin joissain erikoistapauksissa. Teoria on kuitenkin hyvä tuntea.

Kullakin rekursiokierroksella kutsun parametrit ovat kutsun omia - siis muuttuja $1 viittaa jokaisella kierroksella senkertaisen kutsun $1:een.

BASH-tulkki pitää kirjaa funktiokutsuista matriisissa nimeltä: FUNKNAME.

Koodia: [Valitse]
#!/bin/bash
function kertoma () {
if [ "$1" -gt "1" ] # rekursiossa pitää ehdottomasti olla relkursion lopetusehto.
then
i=$(expr $1 - 1)
j=$(kertoma $i)
kertoma= echo "$1*$j" | bc | tr -d '\\\n'
echo $kertoma   
else
echo 1
fi
}
read -p 'mistä luvusta se kertoma lasketaan: ' x
kertoma $x

vertaapa nopeutta otettaessa kertoma luvusta 500: echo 500 | dc -e '?[q]sQ[d1=Qd1-lFx*]dsFxp' | tr -d '\\\n'

***

Murheellista tuo BASH:in kirjastojen vieroksuminen, sillä funktion tekeminen melkein mihinvaan on suorastaan rikollisen yksinkertaista - nimittäin tehdä ne tavalla joka on kymmeniäkertoja turhan hidas, virheellinen, sisältää turhia rajoituksia ja henkilökohtaisia luuloja. Joten nuo pienet funktiot ovat koneen kanssa taistelua mikä ei tosiaan kuulu yhtään mihinkään ja masentaa vain. Niinpä BASH kaipaisi ehdottomasti kirjastoja - tai siis että niitä kirjastoja olisi kirjoitettu ja että niitä käytettäisiin.

Kaikenlaista  kirjastokamaa löytyy eikä erkkikään muista edes näitä yksinkertaisia. Koska funktioita ei haeta kirjastosta niin tapahtuu seuraavaa: teet skriptiä ja kun saat sen toimimaan menet ja esittelet ylpeänä sen kavereillesi - jolloin se kaatuu heti alussa sillä jokaisessa vähänkin suuremmassa funktiossa on montakin salahautaa -> pienikin muutos dataan ja heti kosahtaa.
Koodia: [Valitse]
function int () { echo ${1%%.*} ;}

function fract () { desimaaliosa=${1##*[,.]}; (( ${#desimaaliosa} == ${#1} )) && echo 0 || echo $desimaaliosa ;}

function abs () { echo ${1//-/} ;}
 
function floor () { [[ ${1//[^-]/} ]] && m1=-1 || m1=0; kokonaisosa=${1%%[,.]*}; echo $(($kokonaisosa+(1*$m1))) ;}

function ceil () { [[ ${1//[^-]/} ]] && m1=0 || m1=-1; kokonaisosa=${1%%[,.]*}; echo $(($kokonaisosa-(1*$m1))) ;}


77
BASH tuntee ainoastaan kokonaisluvut ja niissäkin luvuissa saa olla korkeintaan noin 19 numeroa. Tuo numeromäärä ei riitä mihinkään, mutta BASH sunniteltiinkin käyttämään matematiikkaongelmissaan kunnon matematiikkaohjelmia.

Mutta yksinkertaisissa matematikkaongelmissa BASH on silti nopeampi kuin nuo matematiikkaohjelmat. Ihan ensimmäiseksi rajoitteeksi tulee pieni numeromäärä ja aluksi tehdään kaksinkertaisen numeromäärän summain kaikenmerkkisille kokonaislvuille joten tätä voi käyttää vähennyslskussakin. Toiminta-aika on luokka 1ms.
             
function addd () { # kokonaislukujen summain kaksinkertaisella numeromäärällä
luku1pituus=${#1}
luku2pituus=${#2}
luku1=00000000000000000000000000000000000000$1
luku2=00000000000000000000000000000000000000$2
luku1=${luku1: -36}
luku2=${luku2: -36}
# echo $luku1
# echo $luku2
luku11=${luku1:0:18}
luku12=${luku1:18}; luku12=${luku11//[^-]/}$luku12
luku21=${luku2:0:18}
luku22=${luku2:18}; luku22=${luku21//[^-]/}$luku22
# echo a$luku11' 'b$luku12
# echo c$luku21' 'd$luku22
tokaluku=$((10#$luku12+10#$luku22))  # Kun käy niin että tokaluku vuotaa yli lisätään ekalukuun 1 ja #leikataan tokaluvusta ensimmäinen merkki pois (se on se ylivuotonumero ja sen arvo on silloin 1)
((10#${#tokaluku} > 10#${#luku12} || 10#${#tokaluku} > 10#${#luku22} )) && ylivuoto=1 || ylivuoto=0
(( $ylivuoto )) && tokaluku=${tokaluku:1}
# echo $ylivuoto
[[ ${luku11//[^-]/} == - && ${luku21//[^-]/} == - ]] && ekaluku=$((10#$luku11+10#$luku21-$ylivuoto)) ||ekaluku=$((10#$luku11+10#$luku21+$ylivuoto))
(( $ekaluku )) && echo $ekaluku$tokaluku || echo $tokaluku ;}

tarkistus:
addd 55555555555555555555555555555555555 55555555555555555555555555555555555
pitää tulla: 111111111111111111111111111111111110

tai:
addd 55555555555555555555555555555555555 -55555555555555555555555555555555555
pitää tulla: 0

tai:
addd -55555555555555555555555555555555555 -55555555555555555555555555555555555
pitää tulla: -111111111111111111111111111111111110

tai:
addd 55555555555555555555555555555555555 555
pitää tulla: 55555555555555555555555555555556110

tai:
addd 555 555
pitää tulla: 1110

tämän kaksoistarkkuuden saisi desimaalilaskuihinkin. Niinkuin kertolaskuunkin. Ja voisi siirtyä 3:nkertaiseen numeromäärään ja siitä ylöspäin. Ja skriptit toimisivat edelleen kohtuullisen nopeasti. Ja tämä olisi toiminut jo silloin kun asialla oli jotain merkitystä käytännössäkin.

aivan varmasti skriptissä on vielä korjattavaa - niinkuin missähyvänsä skriptissä.

***

Jakolasku kokonaisluvuille. Tulos voi kuitenkin olla desimaaliluku ja se esitetään 16:lla desimaalilla.

Taas kertaalleen tämän tekemisessä vaivasi "ajatukset jämähdyttävä väärä luulo" - ja kuvittelin kauan että BASH:ille saa vain matematiikkanero tehtyä skriptin jakolaskuun - ja sekin on tuhatrivinen ja hidas - enkä siis uskaltanut yrittääkään. Mutta itseasiassa skriptinteossa ei nokka kauaa tuhissut.

Tämä jakolasku ei toimi oikein kaikissa tilanteissa mutta on tosiyksinkertainen, desimaaleja voi olla paljon ja lisäksi skripti on helppo ymmärtää. Desimaalijakolaskussa virhetoiminta poistetaan, mutta sen skripti on senkintakia paljon sotkuisempi.
Koodia: [Valitse]
function divide () {
luku1=$1
luku2=$2
kokonaiset=$((10#$1/10#$2)); jakojaannos="$((10#$1%10#$2))"
desimaalit="$(((1000000000000000*10#$jakojaannos)/10#$2))"
echo $kokonaiset.$desimaalit ;}
tarkistus:
divide 9000000000000001 3
Pitää tulla: 3000000000000000.33333333333333333

***

Jakolasku desimaaliluvuille. Kokeile, tämä on virtuooseille niin nyöryyttävää että tekevät parhaansa tämän torpeedoimiseksi.

Tämä täydentää desimaalilaskut sillä yhteenlaskua voi käyttää myös vähentämiseen. Kaikkien desimaalilaskujen suoritusnopeus on luokkaa 1ms kun matematiikkaohjelmia käytettäessä desimaalilaskut kestävät luokkaa 4ms - melkein koko 4ms menee siihen kun matematiikkaohjelmaa luetaan levyltä.

Desimaaliluvulla voi jakaa toisen desimaaliluvun kokonaisluvuille sopivalla menetelmällä kun poistaa luvuista desimaalipisteet - numerot tulevat silloin edelleen oikein.

Mutta kokonaisosa tulee heti aluksi oikein vain kun desimaaliosat ovat yhtäpitkät - siis jos ne eivät ole yhtäpitkät niin lyhyemmän desimaaliosan omaavan perään kirjoitetaan tarpeeksimonta nollaa senjälkeen kun on varmistettu että niissä molemmissa on desimaalipiste ainakin lopussa.
 
Tällä desimaaliosien yhtäpituuden varmistamisella on toinenkin syy: desimalit lasketaan muuten väärin.

Jokatapauksessa desimaaleja laskettaessa on vaikeutena se että etunollat katoavat eikä matematiikka kerro montako niitä etunollia on ollut. Mutta kun kirjoittaa desimaalien laskemisen jälkeen niiden eteen paljon nollia ja katkaisee muodostuneen desimaalijonon perästälukien siihen mittaan joka desimaaleille on varattu ovatkin etunollat näennäisesti tallella.

Sitten vain tulostetaan kokonaisosa <desimaalipiste> desimaaliosa

Skriptin loppuhionta on iäisyyskysymys: esimerkiksi desimaalien määrää saisi varmasti lisättyä ja oikeellisuuden tarkistaminen kestää vuositolkkua.

Tämä olisi toiminut aina - tässä ei ole mitään uutta tai keksinnöllistä - suuret virtuoosit ovat "unohtaneet" ratkaisun sillä itseasiassa tämä on noviisi-tasoa ja sopivien käskyjen löytyminen osoittaa että tämä on tehty jo. Mutta tämä kaipaisi kipeästi parantamista varsinkin desimaalimäärän suhteen - suuret ulkomaiden virtuoosit pystyisivät siihen ja toivottavasti nyt yrittävät päästä eroon narrinhatusta jonka ne tässä sai sillä tämä suorittaa "mahdotoman tehtävän" huippunopeasti.
Koodia: [Valitse]
function jaa () {
luku1=$1
luku2=$2
[[ ${luku1//[^.]/} ]] || luku1=$luku1"."
[[ ${luku2//[^.]/} ]] || luku2=$luku2"."
desimaaliluku1=${luku1##*.}
desimaaliluku2=${luku2##*.}
(( ${#desimaaliluku2} >= ${#desimaaliluku1} )) &&
{ apu=$desimaaliluku1"0000000000000000000"; desimaaliluku1=${apu:0:${#desimaaliluku2}} ;} || { apu=$desimaaliluku2"0000000000000000000"; desimaaliluku2=${apu:0:${#desimaaliluku1}} ;}

kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

luku1ilmandesimaalipistetta=$kokonaisosa1$desimaaliluku1 #; echo $desimaaliluku1
luku2ilmandesimaalipistetta=$kokonaisosa2$desimaaliluku2 #; echo $desimaaliluku2
 
kokonaiset=$((10#$luku1ilmandesimaalipistetta/10#$luku2ilmandesimaalipistetta))
jakojaannos=$((10#$luku1ilmandesimaalipistetta%10#$luku2ilmandesimaalipistetta))
desimaalit=$(((1000000000*$jakojaannos)/$luku2ilmandesimaalipistetta))
desimaalit=000000000000000$desimaalit
echo $kokonaiset.${desimaalit: -9} ;}
Tarkistus:
jaa 6666666666.250000002 2
täytyy tulla: 3333333333.125000001

tai:
jaa 1 1000000000
täytyy tulla: 0.0000000001

tai:
jaa 1233457890123.23 .123456
pitää tulla: 9991072852864.421332296
- lähellä lukualueen ylärajaa
- matematiikkaohjelma: bc <<< "scale=9;1233457890123.23/.123456"  tulostaa: 9991072852864.421332296

***

Desimaalijakolasku-skriptin uuden version suoritusnopeus on 0.4ms vaikka se muodostuu 21 lauseesta. Oikeantyyppisistä käskyistä muodostuvat BASH-skriptit voivat olla todella nopeita, viisikymmentäkertaa nopeampia kuin normaalisti.

Syy uuden version tekemiseen olikin se että halusin varmistaa että tuollaisten käskyjen suorittamisessa BASH on nopea.

Jokatapauksessa desimaalijakolaskun tuloksessa esitettävien desimaalien lukumäärä on vanhassa versiossa vain 9. Uudessa versiossa nostin desimaalien lukumäärän 17:ään. Lisäämisen vaatima laskenta ei hidastanut havaittavasti.

Nyt alkaa varmistua että BASH on täysin erilainen kuin miksi se esitetään. Minkähäntakia sitä roimitaan? Antaako se liikaa vapauksia eivätkä isoiset pidä siitä?

***

Saapa nähdä kuinka neliöjuuren laskeminen onnistuu - kovin rajoitettua se kyllä on, luokkaa 7 desimaalia. Veikkaanpa ettei se montaakaan millsekuntia kestä. Ehkä se on vain toive.

Täytyy tosiaan vetää länkiä kaulaan. Kyllä se lopulta onnistui mutta nopeudesta en enää puhu mitään sillä koodi on kammottavan suuri - mutta toisaalta desimaalipilkkujen raahaaminen matkassa tekee koodista pitkän - eiköhän se koodi tänne kohta ilmesty mutta välillä muuta.

***

BASH on piikki isojen putiikkien ahterissa sillä BASH antaa koneen käyttäjänsä leikkikaluksi. Kyseessä eivät ole nuo tekstinkäsittely tai numeronmurskaus-tehtävät vaan se että BASH:illa on syvällinen liitto käyttöjärjestelmän kanssa. BASH:ia käyttäessäsi hallitset omaa konettasi ja isojen putiikkien mielestä koneesi hallinta kuuluu yksinomaan heille - nimenomaan sinulla itselläsi ei saa olla mitään oikeuksia eikä edes kykyjä tarkistaa mitä koneessasi tehdään. 

Ja BASH:in kehittäjät auttavat noita putiikkeja ansiokkaasti: oletkos koskaan tullut miettineeksi miksi noita deb:bejä ja PPA:oita harrastetaan niin kiihkeästi? Olisikohan sillä jotain tekemistä senkanssa että BASH:illa menee tosihuonosti? Ja isot putiikit nauravat partaansa kun saivat distrot älytettyä yrittämään tuhota BASH.

Sillä nuo hienot palikat tuhoavat yritteliäisyyden koska kaikki mitä aikaansaat ovat noiden palikoiden rinnalla todella hitaita virhepesiä. Ja kun yritteliäisyys kuolee niin käy juuri niin kuin on käynyt BASH:ille: perustaidotkin katoavat. Ja niinpä ollaan päädytty tilanteeseen josta nouseminen on todella vaikeaa - ja isot putiikit yrittävät estää BASH:ia saamasta entistä asemaansa.

BASH:ia lyödään kuin vierasta sikaa virtuoosienkin taholta. Tosiasia kyllä on että BASH on hidas ja opetustarkoituksiin tehtynä siinä on niinmonta kompastuskiveä ettei kukaan selviä niistä kaikista. Silti BASH:in kanssa tässä nyt eletään eikä siitä päästä eroonkaan.

Mihinkä todelliset virtuoosit pystyisivätkään jos eivät kokoajan vain toistaisi hypnoosiin vaipuneina ettei BASH kykene?

Ilmeisesti montakin läpimurtoa on tehty näiden "BASH ei kykene" väittämien suhteen. Vaikka useimmat väitteet lienevät vain omaakehua niin jotkut taitavat pitää paikkansa. Mutta alkaapäälle nuo ratkaisut ovat ilmanmuuta hitaita, reikäisiä ja vaikeasti käsitettäviä hirviöitä eikä niitä kehdata esittää eikä jakseta jalostaa - sillä jalostaminen on niin rankkaa että terveys menee. Tuo kehtaaminen on erittän käsitettävää: esittämääsi raadellaan säälittä ja narrinhattua ei kukaan halua kantaa.

78
Kaikenlaiseen tulee näköjään syyllistyttyä. Mutta en hölmöile ensikertaa joten on helppoa tunnustaa.

Minunmielestäni BASH:in viehätys onkin siinä että kaikessa on miljoona mahdollisuutta virheille - mutta virhetilanteita voi estää muodostumasta. Kaikkia mahdollisia virhepaikkoja ei ole mahdollista huomata mutta minkähyvänsä huomaamansa virhepaikan voi välttää.

Desimaalilukujen vertaaminen on sinälläänkin mukava apu nopeutensa tähden mutta sen varsinainen merkitys on siinä että se avaa oven nopeaan desimaalilaskentaan.

Matematiikkaohjelmat kuten bc viettävät vertailussa aikaa viisikertaa enemmän, mutta sen korvaukseksi niilä monia erikosiominaisuuksia vertailun tueksi. Joten monimutkaiset vertailut täytyy edelleen suorittaa  bc:llä.

Vertailufunktio toimii millisekunnissa vaikka onkin kooltaan hirviö. Funktion pituus ei vaikuta nopeuteen paljoakaan sillä koska käskyjä on vain yksi rivi niin se tulkataan kokonaisuudessan C-kieliseksi ohjelmaksi ja senpäälle aikaa kuluttaa vain yksi funktiokutsu. Tämmöinen vertailufunktio on nyt - mutta sitä täytyy korjata vielä montakertaa: 
Koodia: [Valitse]

function max () { a=${1//+/}; b=${2//+/}; b=${b//-./-0.}; a=${a//-./-0.}; [[ ${a%.*} == $a ]] && a=$a'.0';[[ $b{%.*} == $b ]] && b=$b'.0'; [[ ${a%.*} == '' ]] && a='0'$a; [[ ${b%.*} == '' ]] && b='0'$b; ((0${a%.*} == 0${b%.*})) && { ((${a//[^-]/}1${a##*.}${b##*.} > ${b//[^-]/}1${b##*.}${a##*.})) && echo $a || echo $b ;} || { ((${a%.*} >= ${b%.*})) && echo $a || echo $b ;} ;}

# kutsuesimerkkejä vaikeista vertailuista (muuten kun kummankin luvun etumerkin vaihtaa niin valitunkin pitää vaihtua):
max 2222222222222222.000000000000000001 2222222222222222.000000000000000002
# max .08 +.079
# max -.08 +.079
# max +.99 1

- vertailu tehdään bc:llä yksinkertaisimmillaan näin: bc <<< "$a > $b" joka tulostaa totuusarvon 0 tai 1.
- tekstijonona vertaaminen onnistuu sekin ihan hyvin. Sekin on nopea, hyväksyy desimaalit ja rajattomasti numeroita, ei välitä vaikka desimaalipiste olisikin pilkku eikä ole aina kovin kranttu vieraiden merkkienkään suhteen. Mutta se ei toimi oikein etumerkkien suhteen, ei ole niin nuuka missä se desimaalipiste on ja voi höpertää muutenkin, joten täytyy miettiä kaksikertaa missä sitä käyttää. Siis tarkoitan tämäntyyppisiä vertailuja:
[[ 2.19mW < 2.20mW ]] && echo 'eka on pienempi'

***

Desimaalilaskenta. Ensin yhteelasku, ja alkuunsa vain jotenkin toimiva periaate:
Koodia: [Valitse]
luku1=1.2; kokonaisosa1=${luku1%%.*}; desimaaliosa1=${luku1##*.}
luku2=3.4; kokonaisosa2=${luku2%%.*}; desimaaliosa2=${luku2##*.}

kokonaisosiensumma=$(($kokonaisosa1+$kokonaisosa2))
desimaaliosiensumma=$(($desimaaliosa1+$desimaaliosa2))

echo $kokonaisosiensumma.$desimaaliosiensumma

Mutta desimaalien yhteenlaskemisessa on sekin ongelma että kun yhteenlaskettavien desimaalien määrä on erilainen niin yhteenlaskettaessa tulee melkoinen virhe mikäli desimaalilukumäärät eroavat. Sen korjaukseksi pikkuisen huonolla mutta yksinkertaisella tavalla voi lisätä kummankin desimaaliluvun loppuun niin monta nollaa kuin toisessa on merkkejä sillä silloin ne ovat yhtäpitkiä:
Koodia: [Valitse]
desimaaliosa1pituus=$(echo ${#desimaaliosa1}); desimaaliosa1=$desimaaliosa1$(printf "%0"$desimaaliosa2pituus"d")
desimaaliosa2pituus=$(echo ${#desimaaliosa2}); desimaaliosa2=$desimaaliosa2$(printf "%0"$desimaaliosa1pituus"d")

Seuraava korjattava on desimaaliosan ylivuoto. Ylivuoto on otettu huomioon jo perusasetelmassa, siinähän jo alkujaan on kummankin desimaaliosan alussa suojanumerona 1 joten ylivuotonumerona on 2. Siis kun tapahtuu todellista ylivuotoa niin ylivuotonumeroksi tulee 3. Muuta ei tarvitse tehdä kuin lisätä desimaaliosan ylin numero kokonaisosan lopputulokseen ja vähentää 2. Siis viimeinen lause muutetaan muotoon:
Koodia: [Valitse]
echo $(($kokonaisosiensumma+${desimaaliosiensumma:0:1}-2))."${desimaaliosiensumma:1}"

***

"BASH ei tunne desimaalilukuja ja niitä ei voi käyttää matemaattisissa tehtävissä - niitä ei voi laskea yhteen, kertoa eikä mitään muutakaan matemaattista. Ne ovat vain tekstijonoja vailla matemaattista merkitystä". Se siitä pilkunviilaamisesta sillä myös BASH:issa on käsitelty desimaalilukuja aina - eikun tekstijonoja.

Tein tähän desimaalimatematiikkaan parikin erilaista skriptiä yhteenlaskusta - muuta ei niitä kannata esitää sillä niillä on vielä paljon rajoitteita vaikka ne toimivatkin muuten hyvin. Nuo rajoitteet voisi kiertää paremmalla koodilla, mutta ei taida kannattaa - ei BASH:in matematiikasta hyvää saa tekemälläkään - luultavasti. Mutta loppupäätelmä totisesti kannattaa esittää: tämä on kokonaisuudessaan aluetta joka on tarkkaan suunniteltu BASH:in alkuperäisten kehittäjien toimesta. Siis seuraan joidenkuiden jalanjäljillä ja näitä BASH:in desimaalilaskimia on ollut jo aikoinaan - miksi niistä ei ole kerrottu en käsitä. Vai onko niistä kerrottu mutta on miellyttävämpää kirjoittaa BASH-pitfail:eista?

***

Taas kertaalleen ällistyin sitä kuinka BASH:illa voidaan ratkaista mikähyvänsä ongelma monin eritavoin huolimatta väitteistä ettei onnistu ollenkaan. Esimerkiksi desimaalilukujen vertailussa edellinen nopeuskuningashan oli:
Koodia: [Valitse]
function max () { a=${1//+/}; b=${2//+/}; b=${b//-./-0.}; a=${a//-./-0.}; [[ ${a%.*} == $a ]] && a=$a'.0';[[ $b{%.*} == $b ]] && b=$b'.0'; [[ ${a%.*} == '' ]] && a='0'$a; [[ ${b%.*} == '' ]] && b='0'$b; ((0${a%.*} == 0${b%.*})) && { ((${a//[^-]/}1${a##*.}${b##*.} > ${b//[^-]/}1${b##*.}${a##*.})) && echo $a || echo $b ;} || { ((${a%.*} >= ${b%.*})) && echo $a || echo $b ;} ;}

Sille löytyi koodiltaan ja toimintaperiaatteeltaan erilainen voitaja joka toimii vielä nopeammin ja sallii melkolailla rajoittamattoman desimaalimäärän vertailtaviin lukuihin:
Koodia: [Valitse]
function max () { [[ ${1//[^-]/} ]] && m1=-1 || m1=1; [[ ${2//[^-]/} ]] && m2=-1 || m2=1; [[ ${1//./} == $1 ]] && a=$1".0" || a=$1; [[ ${2//./} == $2 ]] && b=$2".0" || b=$2; a=${a//+/}; b=${b//+/}; b=${b//-./-0.}; a=${a//-./-0.}; ((10#${a%.*} == 10#${b%.*})) && { (($m1*10#${a#*.} >=  $m2*10#${b#*.})) && echo $1 || echo $2 ;} || { ((10#${1%.*} > 10#${2%.*})) && echo $1 || echo $2 ;} ;};
# ja esimerkkikutsu:
max -1111111111111111111111111111111111111111.000000000000000000000000000000002 -1111111111111111111111111111111111111111.000000000000000000000000000000003

- ihan varmasti on edelleen tilanteita joissa funktio toimii väärin sillä koska BASH on opetuskieli on siinä erittäin runsaasti kompastuskiviä - ja semmoinenkin ilmiö on että jos suorittaa korjauksia väärässä järjestyksessä tulevat ne vanhat virheet pahempina takaisin joten loppuelämä kuluu rattoisasti testatessa alusta alkaen jokaisen korjauksen jälkeen.

- samoin on ihan varmaa että parempikin ratkaisu löytyy.

***

Desimaalilaskenta aivan uudestaan - ei ne vanhat väärin ole, kyllä niissä ajatuksentynkää on.

"BASH ei tunne desimaalilukuja ja niitä ei voi käyttää matemaattisissa tehtävissä - niitä ei voi laskea yhteen, kertoa eikä mitään muutakaan matemaattista. Ne ovat vain tekstijonoja vailla matemaattista merkitystä". Tarkoituksena on luoda mielikuva etä BASH ei pysty desimaalilaskentaan, mutta niin ne eivät sanoneet sillä tiesivät itsekin että kyllä pystyy - ja joissain tilanteissa säällisen nopeastikin. Hyvin harvoin desimaalilaskentaa kannattaa silti nykyisellään  käyttää ja saako siitä koskaan kelvollista yleisempään käyttöön on toinen asia - mutta kun esitetään asia niin ettei BASH kykene niin se on törkeää sillä senjälkeen ei edes yritetä. Nimittäin kaikessa on aina parantamisen varaa, ideoista se vain riippuu - ja teoriatiedoistakin.

Ilmanmuuta BASH:iin on aikoinaan tehty desimaalilaskennan funktioita - sillä jokaisessa funktioden tekemisen vaiheessa tuntuu siltä että nyt kuljetaan jonkun jalanjäljissä. Joskus tarvittavat funktiot ovat sangen yksinkertaisia mutta esimerkiksi jakolaskufunktion tekeminen vaatisi matematiikkaneroa - mutta eiköhän joku ole tehnyt jakolaskuunkin funktion. Kukahan on aktiivisesti "unohtanut" että desimaalilaskentakin toimii - ja uskotellut muillekin niin?

Siten käytäntö: koska käyttäjä ei voi määritellä + merkkiä uudestaan on yhteenlaskuun suorittamiseksi käytettävä funktiokutsua: add luku1 luku2  - ja mikäli skriptissään haluaa suorittaa yhteenlaskun niin skriptissä merkintä on tyypiltään: vastaus=$(add luku1 luku2)

Desimaaliyhteenlaskun funktio toimii periaatteessa tällätavoin: laskettaessa yhteen lukuja, esimerkiksi 222 ja 0.123 muokataan luvut siten, että niiden desimaaliosissa on yhtämonta numeroa: 
                         222.000
                           0.123
Tämänjälkeen lasketaan erikseen yhteen kokonais- ja desimaaliosat ja tulostetaan ne desimaalipisteen eroittamina peräkkäin. Desimaaliosia yhteenlaskettaessa etunollat poistettaisiin tulkin toimesta ellei sitä estettäisi: koska desimaaliosat ovat yhtäpitkät niiden molempien eteen lisätään suojanumeroksi 1 ja tulostettaessa jätetään tuo suojanumero pois joten "etunollat" säilyvät mutta noita suojanumeroita ei tulosteta. Desimaaliosan ylivuoto yhteenlaskussa otetaan huomioon siten että kokonaisosaan lisätään desimaaliosan ensimmäinen numero (=se suojanumero - ja ylivuoto kasvattaa sitä) ja vähennetään tuloksesta 2.
- sekä kokonais- että desimaaliosassa kummassakin saa olla korkeintaan 19 numeroa.
- mikäli lukualue ylittyy ei edes tule varoitusta.
- toimintanopeus jää noin 4:ään millisekuntiin - ehkä vähän paremmaksi kuin mitä matematiikkaohjelmat tarjoavat.
Koodia: [Valitse]
function add () {
# reset # testauksen aikana tarpeen mutta käytössä kommentoitava
# [[ ${1//[^-]/} ]] && m1=-1 || m1=1; [[ ${2//[^-]/} ]] && m2=-1 || m2=1 # vähennyslaskun alkulause
luku1=$1
luku2=$2
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellaien mikäli ei jo ole
desimaaliluku1=${luku1##*.}
desimaaliluku2=${luku2##*.}
 
(( ${#desimaaliluku2} >= ${#desimaaliluku1} )) &&
{ apu=$desimaaliluku1"0000000000000000000"; desimaaliluku1=${apu:0:${#desimaaliluku2}} ;} || { apu=$desimaaliluku2"0000000000000000000"; desimaaliluku2=${apu:0:${#desimaaliluku1}} ;}
# echo a$desimaaliluku2' 'b$desimaaliluku1 ; read # testatessa tämä on tarpeen

kokonaisluku1=${luku1%%.*}
kokonaisluku2=${luku2%%.*}
desluku=$((1$desimaaliluku1+1$desimaaliluku2)) # 1 on se suojanumero
echo $((10#$kokonaisluku1+10#$kokonaisluku2+${desluku:0:1}-2)).${desluku:1} ;}
koe suurimmalla sallitulla numeromäärällä ja ylivuodolla:
add 1111111111111111111.511111111111111111 1000000000000000000.600000000000000001  # ja se tulostaa:
    2111111111111111112.111111111111111112

***

Päivitin positiivisten desimaalilukujen skriptin. Millisekunnin se enää kestää hakaten 1-4 matematiikkaohjelmia käyttävät laskut. Pitää kai sitten yrittää tehdä siitä versio joka hyväksyy negatiivisetkin luvut.

BASH:in sisäisten käskyjen suorittaminen on ihmeen nopeaa: iso koodiröykkiö vie aikaa vain millisekunnin.

Jatkuva testaaminen puuduttaa ja välillä täytyy ajatella jotakin muuta:

Teoria on että skripti suunnitellaan etukäteen niin hyvin ettei se virheile - sillä korjaamalla ei saa moitteetonta vaan se täytyy synnyttää virheettömänä. Mutta ei skriptiä noin tehdä - harhaluulo tulee siitä että kuvitellaan että skriptinteossa merkitsee jotakin mitä siinä BASH:in kelvottomassa virheviestissä lukee. Niitä ei lueta - se riittää viankorjaukseen että virhe ylipäätään tapahtuu.

En aikaisemmin käsittänytkään että itseasiassa BASH:in mollaajat kaivavat linuxille hautaa - ymmärtämättä että heitä on älytetty.

***

Tällainen tästä BASH:in yhteenlaskusta tuli. Onhan tämä neljäkertaa nopeampi kuin matematiikkaohjelmat, mutta myöntää täytyy että en jaksanut testailla ja silittää ryppyjä joten eihän tämä vielä luotettava ole. Ainoastaan positiivisten desimaalilukujen yhteenlasku on aikahyvin testattu. Mutta tekee naurettavaksi väitteet ettei BASH osaa. 

Tämä kelpaa kaikkiin yhteenlaskuihin - siis kummankin yhteenlaskettavan etumerkki voi olla + tai -. Siis siten tämä kelpaa vähennyslaskuihinkin. Lukualue on 19 numeroa sekä kokonais- että desimaaliosassa.
Koodia: [Valitse]
function add () {
# reset # testauksen aikana tarpeen mutta käytössä kommentoitava
[[ ${1//[^-]/} ]] && m1=- || m1=+; [[ ${2//[^-]/} ]] && m2=- || m2=+
luku1=$1
luku2=$2
luku1=${luku1//-./-0.}; luku2=${luku2//-./-0.}
[[ ${luku1//./} == $luku1 ]] && luku1=$luku1".0" # on tarpeen että luvussa on yksi desimaalipiste
[[ ${luku2//./} == $luku2 ]] && luku2=$luku2".0" # joten lisätään sellaien mikäli ei jo ole
desimaaliluku1=${luku1##*.}
desimaaliluku2=${luku2##*.}
(( ${#desimaaliluku2} >= ${#desimaaliluku1} )) &&
{ apu=$desimaaliluku1"0000000000000000000"; desimaaliluku1=${apu:0:${#desimaaliluku2}} ;} || { apu=$desimaaliluku2"0000000000000000000"; desimaaliluku2=${apu:0:${#desimaaliluku1}} ;}
#echo a$desimaaliluku2' 'b$desimaaliluku1 ; read # testatessa tämä on tarpeen
kokonaisluku1=${luku1%%.*}
kokonaisluku2=${luku2%%.*}
case $m1$m2 in
-- ) desluku=$((1$desimaaliluku1+1$desimaaliluku2)) && apu=$((10#$kokonaisluku1+10#$kokonaisluku2$m2${desluku:0:1}+2)); (( $apu )) && echo $apu.${desluku:1} || echo -0.${desluku:1} ;;
++ ) desluku=$((1$desimaaliluku1+1$desimaaliluku2)); echo $((10#$kokonaisluku1+10#$kokonaisluku2+${desluku:0:1}-2)).${desluku:1} ;;
+- ) desluku=$((1$desimaaliluku1-1$desimaaliluku2)) && apu=$((10#$kokonaisluku1+10#$kokonaisluku2$m2${desluku:0:1})); (( $apu )) && echo $apu.${desluku:1} || echo -0.${desluku:1} ;;
-+ ) desluku=$((1$desimaaliluku1-1$desimaaliluku2)); echo $((10#$kokonaisluku1+10#$kokonaisluku2+${desluku:0:1})).${desluku:1} ;;
esac
}


Kokeita:
add 1111111111111111111.511111111111111111 1000000000000000000.600000000000000001  # ja se tulostaa:
    2111111111111111112.111111111111111112

add 08.08 08.08

***

Desimaalilukujen kertominen suoritetaan BASH:issa niin että desimaalipisteet poistetaan ja kerrotaan muodostunet kokonaisluvut. Syntyvässä numerosarjassa on oikeat numerot ja mikäli ratkaisee ongelman mihin desimaalipiste sijoitetaan niin tulos on oikea. Koodina tämä on:
Koodia: [Valitse]
function multiply () {
luku1=$1
luku2=$2
[[ $luku1 == 0 || $luku2 == 0 ]] && echo 0 && return
[[ ${luku1//[^.]/} ]] || luku1=$luku1".0"
[[ ${luku2//[^.]/} ]] || luku2=$luku2".0"
desimaaliosa1=${luku1##*.}
desimaaliosa1pituus=${#desimaaliosa1}
desimaaliosa2=${luku2##*.}
desimaaliosa2pituus=${#desimaaliosa2}
desimaalejakaikkiaan=$(($desimaaliosa1pituus+$desimaaliosa2pituus ))
tulo=$((${luku1//./}*${luku2//./}))
echo ${tulo:0: -$desimaalejakaikkiaan}.${tulo: -$desimaalejakaikkiaan} ;}

Vertaa itse:
time multiply 9.65438761 -.3723476896
time bc <<< "scale=18;9.65438761*-.3723476896"
- mutta toisaalta bc:ssä merkkiluku on rajoittamaton ja siihen saa helposti mukaan myös sinit, logaritmit ja muut.
- desimaalien maksimimäärä on tässä BASH-toteutuksessa pieni. Se korjaantuu tulevaisuudessa kaksoistarkkudella. Tai nelois ...

79
Muuttujien vertaaminen ei onnistu matemaattisella vertailulla:
Koodia: [Valitse]
a=2; b=1; (( $1 > $2 )) && echo 'eka on suurempi' # eikä mikään vaihtoehto onnistu tuolle > merkille.
# se onnistuu kylläkin kun kirjoittaa:
(( 2 > 1 )) && echo 'kaksi on suurempi kuin yksi'
Mutta BASH kykenee vertailemaan kokonaisluku- tai desimaalimuuttujiakin ihan peruskäskyillään, ensiksi yhtäsuuruus:
Koodia: [Valitse]
a=2.1; b=2.1; ((${a//./} ^ ${b//./})) || echo yhtäsuuria
# mutta: a=21.1; b=2.11; ((${a//./} ^ ${b//./})) || echo yhtäsuuria  # menee väärin. Korjaus tähän:
a=21.1; b=2.11; ((${a%.*} ^ ${b%.*})) || ((${a//./} ^ ${b//./})) || echo yhtäsuuria
seuraava vääriä tuloksia aiheuttava on .... ja sen korjaus .... Kyllä BASH kykenee kaikkiin korjauksiin mutta kaavat tulevat jatkuvasti pitemmiksi ja mahdottomammiksi muistaa ja loogiset operaatiot saattavat skriptintekijän sekaisin. Nopeus ei laske paljoakaan

***

Sitten varsinainen vertailu. Siitä kannattaa tehdä funktio. Ei se super-nopea ole, mutta eipä kuhnailekaan. Eiköhän siinä ole vielä parsimista, mutta tämmöinen se nyt on:
Koodia: [Valitse]
function onkoekasuurempi () { ((0${1%.*} ^ 0${2%.*})) || (( ${1//./} < ${2//./} )) && echo 0 || echo 1 ;}
#                                                                    ^
#                                            vertailumerkki on tässa | ja sitä voi muuttaa mieleisekseen
# ja sen kutsuesimerkki:
onkoekasuurempi -0.127 -0.126 
Muuten nopeudesta: BASH:in peruskometoja saa olla pitkä rivi ennenkuin  kokonaisus kestää edes yhtäkauan kuin paraskaan ulkoinen ohjelma. Esimerkiksi:
Koodia: [Valitse]
a=-1.01;b=-1.02; echo -e $a'\n'$b | sort -n | tail -1
on hidas ja toimiikin väärin kuten näet kun muutat miinukset plussaksi: plussapuolen suuremman iitseisarvon pitää nimittäin olla negatiivisen puolen pienemmän itseisarvo. Ja tuo pitkä hiviö on montakertaa nopeampikin.

Funktiokutsu vie hyvin vähän aikaa. Funktiokutsuun kuluvan ajan korvaa ylimäärin se että se tekee skriptin rakenteesta selväpiirteisen ja ongelmattoman. Funktiot on syytä tallettaa kirjastoon. Sillä nuo merkinnät ovat tosiaankin niin kummallisia että ellei niitä saa kopioitua jostakin niin käyttämättä jää.
Ja kirjaston toinen nimi on automaattikopioija.

Tämä johtaa aiheseen nimeltään desimaalimatematiikka jota BASH kyllä hallitsee myös. "salamannopeasti" lisäksi; se jää kyllä nähtäväksi pystynkö minä tekemään kaavat sille.

- kaavat ovat tosiaan kauheita vaikka ne suoritetaankin nopeasti. Ja salahautoja olisi runsaasti ja niiden kiertäminen hidasta. Pääsisihän sillä muutamaan millisekuntiin, mutta koska bc toimii ongelmitta jo viidessä milliskunnissa niin toistaiseksi viisainta on sittenkin käyttää seuraavantyylistä:
Koodia: [Valitse]
bc <<< "1.000001+5.27"

***

Vaikka pääohjelmassa ei voi kirjoittaa: (( $1 > $2 )) && ... niin funktiossa se on sallittu:
Koodia: [Valitse]
function max () { (( $1 > $2 )) && echo $1 || echo $2 ;}
 
# ja funktion kutsuesimerkki:
max 5 9

80
Ja samantapaisia hitaita menetelmiä tulee käytettyä monissa tehtävissä: ei kannata käskeä "cat tiedosto | grep jotakin". On nopeampaa toiminnallisesti ja helpommin käsittävääkin käskeä: "grep jotakin tiedosto" - ja sama koskee useimpia muitakin käskyjä: ei kannata käskeä: "cat tiedosto | tail -1" vaan kannattaa käskeä: "tail -1 tiedosto". 

Miksi noitä putkia on alunperinkään opetettu laittamaan jokapaikkaan? 

- awk ja sed ovat yksinään paljon tehokkaampia kuin BASH, mutta BASH:in apulaisiksi niistä ei aina ole sillä ne ovat ulkoisia käskyjä joten niiden lataaminenkin kestää. Niitä kannattaa käyttää vain kun niille annetaan ratkaistavaksi monimutkainen tehtävä.

***

Skriptien nopeuttamiseksi on miljoonia muitakin silkkaa BASH:ia olevia keinoa - eivätkä ne ole kummallisia niinkuin tuo LC_ALL=C joka ei aina edes tehoa. Ja mikäli jollekin toiminnalle löytyy BASH-käsky niin se on myös nopeampi kuin sed ja awk-viritykset. Toisaalta BASH kyllä kompastelee kaikissa esteissä. Esimerkiksi tämmöisiä löytyy:
Koodia: [Valitse]
apu=kilometri; echo ${apu//[a-l]/x}  # tulostaa: xxxomxtrx. Elikä tehdään BASH:issa se muunnos joka
# yleensä tehdään sed:issä. Ja BASH:kin tuntee regex:än, vaikka varsin rajoitetusti. 

apu=kilometri; echo ${apu//kilo/'~'} # tulostaa: ~metri elikä vaihdetaan sananosa eikä merkkejä.

apu=kilometri; echo ${apu#*ilo}                          # tulostaa:metri elikä toimitaan sananosilla
apu=kilometri; apu=$(echo ${apu%*tri}); echo ${apu#*ilo} # tulostaa:me sieltä välistä
# siis kun echotaan niin käsky cut on pahvia.
Samantapaisia on loputtomasti. Ja yleensä ne toimivat matriisienkin kanssa joten niitä voi soveltaa loopittomiin ratkaisuihin - jolloin jo nopeampi nopeutuu lisää. Tosin nuo BASH:illa tehdyt kompastelevat enemmän kun awk ja sed.

***

BASH:in toiminta virhe- ja poikkeustilanteissa:

Mikäli skiptissä viitataan johonkin jota ei ole olemassakaan oletetaan että kutsutaan funktiota jota ei ole määritelty ja kutsutaan automaattisesti funktiota: command_not_found_handle. Koska tuossa funktiossa voidaan tehdä mitävaan skriptintekijä määrää niin sen muodostaminen on skriptintekijän vastuulla. Käytännössä skriptintekijä haukkuu BASH:in kivenrakoon kun ei tiedä tuota funktiota määritellä. Muiden poikkeustilanteiden käsittelemiseen pätee seuraava:

Tehdäänpä koneessa mitähyvänsä niin käyttöjärjestelmä kyllä havaitsee sen ja asettaa vastaavan signaalin. BASH käy jokaise käskyrivin jälkeen tutkimassa määriteltyjen 78:n signaalin tilan ja tekee mitä trap-käskyllä on määrätty tuossa tilanteessa tekemään. Esimerkiksi seuraavankaltaisia trap-komentoja yleensä tehdään:
Koodia: [Valitse]
trap 'echo virherivi:${LINENO[@]};echo ${FUNCNAME[@]} ; read' ERR
trap 'rc=$?; echo "virhekoodi $rc rivillä $LINENO-${FUNCNAME[0]}" ' ERR
trap ' (( $? )) && echo ${FUNCNAME[0]}" palautti virheen:"$?'  RETURN
trap "echo kutsupino: ${FUNCNAME[@]}" RETURN
trap pomppaaTänne SIGINT                    # mitä tehdään kun painetaan CTRL-c (nimi eikä numero)
trap 'echo "Control-C disabled."' 2
trap jälkiensiivous SIGTERM                 # kun skripti loppuu tai tekee suoritusaikaisen virheen
trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG # muuttujanarvon seuraaminen
set -x; trap "echo hit_return;read x" DEBUG                    # käskyrivien suoritus yksi kerrallaan
- trap:peja voidaan kirjoittaa funktioisakin vaikka ne yleensä määritellään skriptin alussa. Esimrkiksi kun virhettä käsitellään voidaan virhekäsittelyn ajaksi määrätä virhekäsittely pois päältä käskyllä: trap - ERR ja palauttaa se vanhassa muodossaan käsittelyn jälkeen - jottei näyttö sotkeutuisi kovin pahasti.

-samoin trapeissa saa olla funktiokutsujakin.

- jos skriptinsuoritus päätyy virheeseen niin tulostuva virheviesti on BASH:issa normaaliakin kehnompi - mutta ei se virheen tarkka ilmoittaminen paljoa auttaisikaan. Nimittäin skriptintekijälle riittää tieto siitä tapahtuuko virhettä vai ei - ja missä se tapahtuu: silloin virheen syyn pitäisi olla melkoselvä.

- BASH ei lopeta skriptinsuorittamista ihan pienistä virheistä vaikka se asettaakin virheen kohdalla muuttujan $? . Yritetään väittää että tällainen toimintatapa ei ole hyväksyttävää ja neuvotaan laittamaan skriptin alkuun: set -e. Mutta siitä on joskus hyötyä ja joskus haittaa.

BASH-skripteissä ei enää käytetä rakennetta: if..then vaan se korvataan rakenteella:
käsky1 && käsky2 -> käsky2 suoritetaan vain mikäli käsky1 onnistuu, tai:
käsky1 || käsky2 -> käsky2 suoritetaan vain mikäli käsky1 epäonnistuu, tai:
käsky2 && käsky2 || käsky3 -> käskyryhmää käytetään siellä missä ennen oli if..then..else .
- usein käsky1 on ehto, esimerkiksi: [[ -f tiedosto ]], mutta ei sen tarvitse olla. Jokatapauksessa käsky: "set -e" olettaa tuon ensimmäisen olevan ehto ja eikä välitä siitä.
- mieti kaksikertaa mihin || milloinkin viittaa, minkä kanssa se OR-funktion muodostaa.

- useimpia muitakaan tulkille annettavia ohjeita ei kannata antaa muuten kuin virhettä metsästettäessä.

***

Skriptin debuggaamisessa ei ole mieltä - kuvaannollisesti huonompi yrittää korjata parempaansa. Hermotkin kiittää jos heität huonostitoimivan skriptin roskikseen ja teet uuden. Vaikka tuleehan siihen debuggaamiseen joskus syyllistyttyä.

Muutenkin on suositeltavaa välillä "hukata" joku skripti ja tehdä se sitten uudestaan sillä yleensä siitä tulee paljon parempi.

Skriptissä kaikkien käytettävien nimien tulee olla valittu niin ettei kommentointia enää juurikaan tarvita. Nimittäin kommentoidessasi skriptiä tajuat että jos se toimisi toisella tavalla nopeutuisi se paljon. Teet siis uuden version ja kommentointi alkaa alusta ja ennenkuin kommentointi on tehty niin taas huomaat että uusi olisi parempi ja ... Ja koska asiallinen kommentointi vie aikaa enemmän kuin koodaaminen niin hiukka idioottimaista se kommentointi on.

Maailmalla monet skriptit on suurella vaivalla kommentoitu pilalle. Sikäli pilalle että koodia ei ole helppo ymmärtää kun pääosa siitä on kommentointia. Kestää nimittäin kauan ennenkuin ymmärrät toisen kommentointia sillä toiselle ilmanmuuta selvä on toiselle vaikeaa joten sitä ei selitetä tarpeeksi - ja toisaalta toiselle vaikea on toiselle itsestäänselvää joten siitä läärätään turhaan ja loputtomasti.

Ihminen tajuaakin koodin sitä hitaammin mitä useammalla rivillä se on esitetty - ja lisää hitautta tulee jos koodi pursuaa näytön toiselle sivulle - varsinkin raamatun kokoinen skripti on vaikea ymmärtää vaikka siinä vain osoitettaisiin että 1+1=2 vielä tänäänkin. Paras esimerkki on jo vanhentunut käsky if..then..else jota ei pitäisi enää käyttää. Koska sitä kuitenkin käytetään niin minkätakia se täytyy jakaa monelle riville kun sen voi kirjoittaa yhdellekin?

***

Nopein keino skriptin nopeuttamiseksi on poistaa siitä tarpeettomia osia - usein poistaminen johtaa huomattavaan nopeutukseen. Sen havaitseminen mikä on tarpeetonta on valitettavasti kokeellista hommaa ja jotta se onnistuisi järjellisessä ajassa tarvitaan työkaluja - aivankuin niitä työkaluja tarvitaan kaikissa kielissä ja ne ovatkin lähes kaikissa kielissä jo perusversiossa - mutta BASH:ista ne on poistettu ja joudut itse luomaan ne uudestaan.

Samalla BASH:ista on poistunut paljon muutakin. Esimerkiksi BASH on pakotettu käyttämääm merkintätapoja joita ihminen ei kykene käyttämään tehokkaasti. Sama merkintätapojen kummallisuus pätee lopulta kaikkiin kieliin mutta niissä inhottavia merkintöjä ei tarvitse käyttää sillä ne on kätketty kirjastoihin.

Mutta paras keino skriptin nopeuttamiseksi on kirjoittaa skripti kokonaan uudestaan käyttäen parempaa toimintaperiaatetta jolloin nopeutta tulee usein paljon lisää, tulee vakautta ja vaikka mitä - kymmenenkertaa nopeampi on arkipäivää. Kannattaako se on toinen asia sillä BASH on sittenkin niin hidas ettei siitä isompaan käyttöön ole - tai hitaaksi BASH:ia ainakin monet väittävät.

Uudet menetelmät tuovat skripteihin uusia ominaisuuksia - esimerkiksi tässä esitetty muistikuva-menetelmä nopeuttaa oleellisesti useimpia isoja tehtäviä. Jos samalla hyödynnetään kuvattua uutta ja yksinkertaista menetelmää yhden tai useamman erityyppisen parametrin palauttamiseksi funktiosta päädytään johonkin aivan uuteen.

***

- koska tuo parametrien palauttaminen on niin perustavalaatuinen asia - ja koska näin loppuunajettuna en kykene asioita loogisesti esittämään niin tässä parametrien palauttaminen on esitetty niin ettei sitä voi ymmärtää väärin vaikka kuinka tahtoisi :
Koodia: [Valitse]
function pienlukujenkertoma () { let $2=$(echo $(($(seq -s* $1)))) ;}

pienlukujenkertoma 6 kuuskertoma
pienlukujenkertoma 20 kakskytkertoma

echo "6  kertoma: "$kuuskertoma
echo "20 kertoma: "$kakskytkertoma

- parametrien palauttamiseen on muitakin keinoja: esimerkiksi kutsurakenne $( ... ) - elikä palautetaan haluttu arvo näytön kautta; toiminta on erittäin nopeaa eikä jätä näyttöön mitään. Sillä on se puute ettei se toimi oikein kun palautettavaa on montariviä.
- tai peräti niin että funktio kirjoittaa kovalevylle ja kutsuja käy sitten lukemassa kovalevyltä. BASH:in hitauden huomioonottaen tämä on ihan käyttökelpoinen keino.
- nämä kaksi keinoa toimivat silloinkin kun funktiokutsu on muotoa: funktionimi () ( ... ) - siis kaarisulut aaltosulkujen tilalla. Kutsu on hitaampi koska se muodostaa ensin oman prosessinsa - mutta lähes kaikki on sitten ihan omaa joten mikään funktiossa tehty ei näy pääohjelmassa. Paitsi levyoperaatiot.

***

Ikuista ratkutusta:
Missään kielessä ei voi tehdä mitään merkittävää ilman kirjastoja. Olisi mielekästä alkaa uudestaan käyttää BASH:iin  kirjastoja sillä BASH:ista ei tulla pääsemään eroon vaikka halu onkin hirveä - ja kirjastoilla varustettuna se olisi ihan kelvollinen kieli - ja alkaa hiljokseen tuntua ettei se ole mahdottoman hidaskaan mikäli käskee oikein. Kuitenkin BASH:in käskyjä olisi syytä parantaa - ilmeisesti se olisi BASH:in kehittäjille helppoakin, mutta nähtävästi rintakarvoja puuttuu - en minäkään kyllä uskaltaisi käskyjä parannella.

***

Käsky sleep toimii millisekunnista ylöspäin eikä se ole kovin tarkkakaan. Tarkemman viiveen saat käskyllä: read -t .xxxxx  - muuttujaa ei tarvitse määrätä. Aika on sekunneissa joten toimitaan 10:stä mikrosekunnista ylöspäin. Pienillä viiveillä toimimista varten tein sitä testaavan skriptin - sadasta mikrosekunnista yhdeksään millisekuntiin:

Skriptissä on kaksi prosessia pyörimässä yhtäaikaisesti. Ensin omia aikojaan tiedoston lukuarvoa tasaisesti kasvattavava prosessi joka työnnetää taustalle käskyllä: & .

Ja sitten normaalina edusta-ajona toinen nopeampi looppi ottamaan tiedostosta ensin näyte, sitten suoritetaan tasaisesti kasvava viive jonka perään otetaan toinen näyte ja tulos on näyte2-näyte1. Tuloksien tulisi kasvaa tasaisesti, mutta koska BASH on ajallisesti horjahteleva niin eihän näin aina ole. Yleensä tämä ei edes tulosta kaikkia määrättyjä; esimerkiksi jos ensimmäinen prosessi kirjoittaa juuri kun toinen haluaa lukea niin BASH luovuttaa.
 
Suuntaus on kuitenkin selvä joten kyllä pienetkin viiveet toimivat ainakin suurinpiirtein - ehkäpä tarkastikin. Joten vaikka tällainen viive on tällähetkellä tarkoitukseton niin on mukava tietää että tulevaisuudessa tällainenkin viive on käytettävissä. 
Koodia: [Valitse]
#!/bin/bash
for (( n=0; n<=99999999; n++ )); do echo $n > file1; done & # tämän loopin pitää olla hitaampi

apu1=0; apu2=0; for m in {0001..0090}; do read -t 0.$m; apu1=$apu2;apu2=$(cat file1); [[ $apu1 && $apu2 ]] && echo -n $(($apu2-$apu1))' ' ; done
kill $! # tämä tappaa alussa määrätyn taustaprosessin - siis tuon jonka perässä on merkki &
rm -f file1

***

Kevennystä:
Voi sen sinin laskea näinkin:
Koodia: [Valitse]
b='$(echo "s(4*a(1)/180*$a)" | bc -l)'; a=30; eval echo $b
- siis muuttujaan laitetaan se kaava. Tekstijono se on kaavakin.

***

Yhdessä asiassa mistään kielestä ei ole BASH:in kilpailijaksi: yhdenrivin skriptit. Kooltaan ne ovat yleensä alle rivinmittaisia, mutta monet ovat kyllä jopa kolmenrivin mittaisia - mutta aina yksi kokonaisus.

Niitä käytetään usein sellaisenaan sillä päätteessä on tavallaan jo rivi: !#/bin/bash. Eikä noille yhdennrivin skripteille haeta koskaan suoritusoikeutta. Mikäli käytettävissä on kunnollinen varasto yhdenrivinskripejä ja niitä osaa käyttää tuleekin käyttäjästä legendaarinen.

Mutta noita yksirivisiä täytyy olla tuhansia ja ne täytyy olla järjestetty niin että löytää tarvitsemansa. Ja hyvin harvasta on siihen sillä kaikki me hallitsemme epäjärjestyksen.

Näyttää lisäksi siltä ettei ole mitään rajaa sille mitä yksirivisellä voi tehdä. Ja ne ovat hämmästyttävän nopeita.

Mutta on yksirivisillä varjopuolensakin: esimerkiksi ne toimivat usein vain jossakin tietyssä distrossa ja joskus ne tarvitsevat sellaisia järjestelmäasetuksia joita harvassa koneessa on.

***

Aikaisemmin käsityksenä oli että vaikka BASH:illa saakin nopeasti nopeasti tehtyä skriptin mihin vaan, niin siitä selviää tutkittavasta vain senverran että voi päättää kannattaako muilla kielillä tehdä kunnollinen ohjelma. Mutta usein BASH-skripti kelpaa kyllä lopulliseksikin versioksi. Esimerkiksi seuraava joka kelpaa pohjaksi monenlaiseen:

Graafisessa xy-esityksessä akseli voidaan esittää joko lineaarisena tai logaritmisena. Esitettäessä jotakin x-akselin ollessa logaritminen saadaan vähimmillä ponnistuksilla sujuva käyrä jos sen esityspisteet sijaitsevat x-akselilla tasavälein.

Koska pisteiden luomisessa tarvittava laskukaava on BASH:issa mahdoton muistaa tein siitä funktion: tämänjälkeen kun tarvitsee tasavälisiä esityspisteitä ei enää tarvitse laskea niitä vaan voi kutsua funktiota tekemään sen.

Mutta logaritmialueella pisteiden muodostamisessa tuli eteen periaatteellinen ongelma: epäilin että peräkkäisten pisteiden suhde on vakio eikä millääntavalla logaritminen. Tämä täytyi varmistaa tekemällä käyrä jossa peräkkäisten x-akselin pisteiden suhde on vakio - ja mikäli väite pitää paikkansa sijaitsevat pisteet tasavälein koko x-akselilla dekadien vahtuessakin - ja lisäksi exponentiaalisen käyrän kuvaaja on suora y-akselin ollessa lineaarinen:
Koodia: [Valitse]
#!/bin/bash
function luoaskellus () { # lineaarisen  asteikon kutsu: luoaskellus alku loppu askel
                         # logaritmisen asteikon kutsu: luoaskellus alku loppu *suhde 
                        # negatiivisilla arvoilla liikutaan imaginääriluvuissa. Siis nollaa ei saa ylittää
unset $1 # varmistus että matriisissa kaikki on tässä funktiossa muodostettua
[[ $(printf '%d' "'${4:0:1}'") != 42 ]] && apu=$(seq $2 $4 $3 | tr '\n' ' ' | tr , . ) || { echo "for (i = $2 ; i < $3; i *= ${4:1})  i" | bc -l > /tmp/delme; apu=$( cat /tmp/delme | tr '\n' ' ') ;}
apu2=($apu)                                # matriisi x-akselin pisteistä   
readarray $1 < <(printf "%s\n" ${apu2[@]})
let $5=${#apu2[@]} ;}                      # pisteiden lukumäärä   
 
function plotlogx () {
# tämä funktio tulostaa ainoastaan tiedostoon: ~/koe.eps eikä näytölle tule mitääm. Tiedosto avautuu tiedostoselaimella
gnuplot -p -e 'set terminal postscript eps color enhanced; set logscale x ;set xlabel "x"; set ylabel "logaritmi"; set output "~/koe.eps"; plot "~/koe"' ;}

luoaskellus askellus .000001 1000000 *1.5 askelluku
rm -f ~/koe; for (( n=0; n<=$askelluku-1; n++ )); do echo -n ${askellus[n]}' ' >> ~/koe; echo $(echo "l(${askellus[n]})") | bc -l >> ~/koe; done

plotlogx

Sivuja: 1 2 3 [4] 5 6 ... 33