Ensiksi täytyy kuitenkin kirjoittaa uusiksi neliöjuuren laskeminen tieteellisestä esitysmuodosta sillä aikaisempi skripti ei useissa koneissa toiminut - tämän pitäisi toimia. Sitäpaitsi uuden version tuloksessa kaikki numerot ovat oikeita - matemaattisesti se merkitsee että viimeisessä numerossa on vain +-0.4999... epävarmuus. Kummallista; jo toinen tapaus jossa BASH on aina täysin oikeassa numeromääränsä puitteissa.
Neliöjuuren laskeminen BASH:in omilla peruskäskyillä on nopeampaa kuin matematiikkaohjelmilla mutta varjopuolena on että tuloksessa on vain 9 numeroa. Neliöjuuri voidaan laskea
kuinkamoni-numeroisesta luvusta tahansa mutta vain 18 merkitsevintä numeroa huomioidaan. Luku voi siis olla olla tyypiltään yhtähyvin:
.0000000000000000000000000000000000000000000000000123456789012345678900000000e+357, 1234567890123456789012345678901234567890123456789000000000000000000000000000e-52 tai yksinkertainen luku 4. Luku josta neliöjuuri otetaan saa olla joko normaalissa tai tieteellisessä esitysmuodossa mutta tulos esitetään aina tieteellisessä esitysmuodossa.
Printf kuuluu uusiin käskyihin ja se on tällaisessa käytössä hyvin hidas. Sen saisi kyllä korvattua joukolla vanhoja nopeita käskyjä mutta muunnoksen tekemisessä olisi kova homma eikä nyt ole aikaa sen tekemiseen - se olisi kyllä yli kaksikertaa nopeampi printf.
Printf on 50 kilo-sanan järkäle joka sössii usein - myös desimaalipisteen kanssa. Aikaisemmassaskriptissä korjasin printf:n käytöstä käskyllä: LANG=C mutta sekään ei tunnu aina toimivan. Muutinkin sen käskyksi: LC_NUMERIC=C joka toimii paremmin.
Kyllähän tämänkin skriptin voi sijoittaa luokkaan temput - eikä tämä olisi muissa kielissä edes hyvä. Mutta sikäli tämä on mielenkiintoinen että BASH:in omat virtuoosit koettavat epäilyttävin keinoin estää tämmöisien skriptien tekemisen BASH:iin. Mieleen tulee että virtuoosit hylkäsivät BASH:in luulojen perusteella ja nyt he yrittävät piilottaa hölmöilynsä.
function sqrt () {
apu=$(LC_NUMERIC=C printf "%.17e\n" $1); mant=${apu%%e*};apu=${apu##*e}; (( $apu & 1 )) && kerroin=10 || kerroin=1; expo=$(($apu/2));
in=${mant//./}"000000000000000000"; in=${in:0:17}
sqrt=2147483648 # 2^31
delta=1073741824 # 2^30 # tämä tulee voida jakaa jatkuvasti kahdella siten että jako menee aina tasan
for ((i=0;i<32;i++)); do
x=$(($sqrt*$sqrt/$kerroin))
(( $x>=$in )) && sqrt=$(($sqrt-$delta)) || sqrt=$(($sqrt+$delta))
delta=$(($delta/2)); (( $delta )) || delta=1 # echo $((1/2)) lasketaan nollaksi - oikein pyöristettynä se on 1
done
echo ${sqrt:0:1}.${sqrt:1}e$expo ;}
juurrettava=27.34567890123456789;time sqrt $juurrettava
***
Nyt kun BASH on melkein kuollut olisi entistä tärkeämpää että kaikki rakentaisivat BASH:ia yhdessä. Ei se ole rakentavaa yhteistyötä että kukaan haukkuu ketään - joten perun aikaisempia väitteitäni: eivät virtuoosit ole hölmöjä vaan halusivat vaan aikanaan muita kieliä korvaamaan BASH:ia sillä BASH on ihan liian vaikea kieli kaikille, myös virtuooseille.
Virtuoosit halusivat siis aikoinaan päästä BASH:ista eroon ja sehän olisi ollut paljon vaikeampaa mikäli BASH:in huomattaisiin toimivan desimaalilukujenkin kanssa ihan hyvin. Mutta tuhoamis halustaan huolimatta virtuoosit eivät ole koskaan sanoneet että desimaalilaskut eivät BASH:illa onnistu - he ovat sanoneet vain että BASH:in matematiikkamoottori ei desimaaleja hyväksy ja sehän on kiistaton tosiasia - ja tosiasia on sekin ettei BASH tunne desimaali-lukutyyppiäkään. Mutta desimaalilaskut lasketaan aina muutenkin kokonaisluvuilla - siis myös matematiikkaprosessoreissa numerot lasketaan kokonaislukulaskimella ja desimaalipisteen paikkaa ylläpidetään erikseen ja kokonaisuutta kutsutaan liukuvan pilkun laskimeksi. Kun BASH:in suhteen tätä ei ole koskaan käyttäjille kerrottu niin virtuoosit ovat itseasiassa johdatelleet käyttäjät tekemään virhepäätelmän siitä ettei desimaaleilla voi laskea - ja jotta varmistuisi ettei kukaan edes epäilisi desimaalilaskujen toimivan tehtiin desimaalilaskuihin soveltumattomia muutoksia käskykantaan, annettiin 'kirjastokielto', väitettiin eval-käskyä pahikseksi - ja väitettiin BASH:ia muutenkin hampaattomaksi ettei kukaan vaan kokeilisi mitään.
- itseasiassa jokaisessa BASH:issa on kirjasto vaikkei siitä koskaan puhuta. Löydät sen käskyllä: declare -f .
- jokaisessa ~/.bashrc-tiedostossa on perus-asennuksessakin todennäköisesti muutama eval-käsky.
- ei desimaali-lukutyyppiä tarvitsekaan olla sillä desimaalilukuhan on sama kuin kaksi kokonaislukua joiden välissä on desimaalipiste - ja desimaalipisteen erottamat kokonaisluvut ovat täysin erillisiä eikä toisen arvo vaikuta toisen arvoon muulloin kuin laskutoimituksissa - joissa vaikutuksen huomioiminen on ysinkertaista. Tekstijonoa voidaan käsitellä myös matemaattisesti mikäli käsiteltävän tekstijonon palanen on ulkoasultaan matematiikka-moottorin hyväksymä kokonaisluku - desimaalilukua voi siis käsitellä kun se jaetaan kahteen kokonaislukuun desimaalipisteen kohdalta ja käsitellään kumpaakin puoliskoa erikseen. Laskutoimituksissa saattaaa syntyä luku joissa desimalipuolelta joutuu hylkäämään ensimmäisen numeron ja lisäämään sen kokonailuvun puolelle - tai muodostamaan luvusta yhden komplementin ja hylkäämällä etumerkin - ja vähentämällä kokonaiosasta yksi.
- mikäli kokonaisluku lyhyempi kuin 18 merkkiä ei ole mitään ongelmaa mutta jos se on pidempi kuin 18 merkkiä jaetaan se palasiin - noiden palasten käsittely on kovin monivaiheista mutta onnistuu sekin.
- päinvastoin tekstijono on paljon parempi kuin desimaaliluku sillä siinähän voi olla merkkejä kuinkamonta tahansa.
- mikäli käsitellään palasiin katkottua lukua saattaa toinen luku alkaa nollalla. Koska BASH mieltää nollilla alkavat luvut oktaaliluvuiksi täytyy palasen eteen kirjoittaa: 10# jotta toimittaisiin jokatapauksessa desimaalijärjestelmässä.
Ja entäpä sitten funktio-parametrit: BASH:in ominaisuus on se että koko skriptin alueella tunnetaan jokaikinen muuttuja joka on määritelty jossakin. Mutta kun mainitaan joku nimi ei se vielä tee siitä muuttujaa: yleensä muuttuja muodostetaan siten että kun nimi mainitaan tarkoituksella että siitä tulisi muuttuja niin kirjoitetaan pääohjelmassa oltaessa: muuttujan_nimi=arvo - mutta funktiossa niin ei voi enää tehdä (esimerkiksi käsky $$parametri_numero=arvo ei toimi). Jos funktioon annetaan muuttuja on se tullessaan vain tekstijono eikä muuttuja - vaikka se olisi alunperin ollutkin muutuja. Mutta sen saa yhdistettyä kutsujan saman-nimiseen muuttujaan jollain käskyistä:
1. numero-muuttujan tapauksessa: let $parametri_numero=arvo
2. tekstijono-muuttujan tapauksessa: read<<<$parametri_numero arvo.
3. BASH lisäksi antaa kirjanpitonsa skriptaajien käyttöön declare-käskyn avulla (ja sallii myös kirjoittaa sinne kirjanpitoon). Ja sitäkautta onkin matriisit viisainta yhdistää nimeen - toimii keino kyllä muillekin muuttujatyypeille.
- yhteenvetona: virtuoosien väitteet etteivät nimiparametrit toimi on pahantahtoinen tulkinta: ne toimivat ihan loistavasti vaikkakin varsin epäsovinnaisesti. Arvoparametreja sensijaan ei useinkaan kannata käyttää vaan toimia nimiparametreilla sillä nimiparametrit huolehtivat parametrin palautuksesta automaattisesti -> mitään ei tehdä mutta silti parametri on 'palautettu' elikä se on muuttunut pääohjelman muistissa. Siis BASH on taas paljon parempi kuin muut kielet - funktioon ei kuljeteta yhtään arvoa eikä myöskään palauteta mitään elikä ei hukata aikaa hommiin jotka on tehty jo.
- voitaisiin kirjoittaa myös: 'eval \$$parametri_numero' mutta virtuoosit torppasivat tämän keinon väittämällä BASH:in toteutusta eval-käskystä pahikseksi - muissa kielissä eval on hyvis.
- virtuoosit ovat perillä siitä että BASH toimii näin eikä sen tarvitse parametreja palauttaa - mutta silti he väittävät edelleen että BASH ei osaa parametreja palauttaa sillä onhan se kirjaimellisesti ottaen ihan totta. Ovat vain vaiti siitä ettei BASH:in tarvitsekaan parametreja palauttaa käytettäessä nimiparametreja - ja koska 'palautus' ei toimi ihan normaalikonstein niin taas kertaalleen on saatu käyttäjät huijaamaan itseään.
- mutta eivät ne eri funktioiden muuttujat aina yhteisiä ole sillä kyllä BASH:issakin tunnetaan paikalliset muuttujat. Lisää vain funktion alkuun lauseen: local muuttuja. Silloin sitä muuttujaa ei tunneta missään muualla - ja toisessa funktiosssa saman niminen muuttuja on täysin eri muuttuja.
- paikallisten muuttujien erikoistapaus on se kun funktio kutsuu itseään. Silloin jokaisen kutsukerran muuttujat ovat ilmanmuuta paikallisia. Tätä rekursiota ei muuten yleensä kannata käyttää sillä se on erittäin hidas ohjelmarakenne - huomasin juuri äsken ettei se muuten olekaan hidas mikäli sitä käyttää siten että tuo rekursio tapahtuu C:ssä - etsipä toteutustapaa näistä kertomuksista yksinkertaisten sqrt funktion toteutuksista.
Samoin skriptien hitauskin on osin tehtyä: skriptit on jo alunperin opetettu kasaamaan väärin ja käyttämään vääräntyyppisiä käskyjä: oikein tehty skripti kutsuu tulkkia vain kerran tai muutaman - mutta väärintehty skripti kutsuu tulkkia lukemattomia kertoja ja siihen se aika kuluu - tulkkaustulos toimii C:ssä ja siellä toiminta on aina nopeaa.
Muihin kieliin verrattuna BASH on sittenkin kelvottoman hidas ja muutenkin omituinen - tuskinpa siitä saa edes välttävän hyvää kieltä vaikka kuinka kaivelisi sen hyviä puolia esiin. Mutta näin ei ollut silloin kolmekymmentä vuotta sitten kun BASH:in rappio alkoi - sillä nämä kaikki olisivat toimineet jo silloin.
Kyllä BASH:ia silti yksi ukko, Chet Ramey, kehittää jatkuvasti ja hyvää työtä hän tekeekin (kai hänellä on tukijoukkojakin?). Luepas BASH 5.2:n uusista ominaisuuksista verkkosivulta:
http://tiswww.case.edu/php/chet/bash/NEWS. Jo ensimmäinen käsky saa epäilemään mistä kielestä on kyse sillä käsky on: malloc. Mutta toisaalta BASH:in käskyttääkin C:tä -> BASH:in malloc on siis ohje kääntäjälle kuinka toimia sillä C:n oma malloc mählii BASH skripteissä.
BASH ei ole saanut koskaan mahdollisuuttakaan kehittymiseen vaan yrityksetkin on torpattu. Mutta edistys ei kavahda ketään - skriptin merkittävimmät kriteerit ovat: toimiiko skripti ja vahingoittaako se ympäristöään. Tuo vahingoittaminen pitää kyllä nykyään varmaankin paikkansa sillä onhan BASH:ia laiminlyöty melkein täysin yli kolmekymmentä vuotta.
Mutta keksijä ja kokeilija ei tosiaankaan ole oikea henkilö saattamaan kieltä iskukykyiseksi, ainoastaan virtuoosit siihen kykenisivät.
***
Desimaalilukujen potenssiin korottaminen onnistuu myös hyvin, tosin toistaiseksi vain positiiviset kokonaislukupotenssit toimivat - mutta jopa desimaaliset potenssit toimisivat, kylläkin erittäin hitaasti logaritmien kautta jos vain tekisi tarvittavan kutsun jo tehdyille monille funktioille - mutta se on BASH:issa niin sotkuinen homma että se kaipaa ihan omaa tarinaansa - negatiiviset potenssit alkaisivat nekin toimia silloin.
Aikalailla vanhan kertaustahan tämä on ja tässä on vain vähän uutta - mutta on pakko käydä tämmöisetkin skriptit läpi sillä vasta tämmöisten jälkeen voi aavistella kuinka toimintaa voisi ehkä parantaa.
Aikaa potenssiin korotuksessa kuluu suurinpiirtein potensin numeroarvon verran millisekunteja elikä BASH skriptiksi toiminta on nopeaa - vaikka muihin kieliin verrattuna toiminta on todella hidasta niin BASH:in kannalta se on jo paljon että homma onnistuu edes hitaasti.
Potenssiin korotettavat luvut katkaistaan 18-numeroisiksi. Ja tuloksen tarkkuus laskee siitäkin pahimmillaan luokkaa numero jokaista potenssin numeroa kohti - joten esimerkiksi kymmenenteen potenssiiin korotettaessa voi pahimmillaan olla täysin varma vain 14:stä oikeasta numerosta - harvoin niin huonosti tosin käy. Ja joskus virhe on mitätön suuremmillakin potensseilla.
Mutta sitten itse skripti (kaikki kirjasto-ohjelmat on syytä liittää mukaan. Tai voisihan nuo funktiot kirjoittaa tiedoston : ~/.bashrc:n perään jolloin ne olisivat aina käytettävissä):
function kerro18 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko monessa paikassa välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
luku1=${1:0:18}; luku2=${2:0:18}
# [[ ${#1} -gt 18 || ${#2} -gt 18 ]] && echo laskettavissa liikaa numeroita && return
$tulosta "annetut numerot: $1 $2"
[[ ${1:0:1} = - || ${2:0:1} = - ]] && merkki=- || merkki=''
[[ ${1:0:1} = - && ${2:0:1} = - ]] && merkki=''
apu1=${luku1//\-/}; apu2=${luku2//\-/}
desimaaliosa1=${luku1##*.};
desimaaliosa2=${luku2##*.};
[[ ! ${apu1//[^.]/} ]] && desimaaliosa1=''
[[ ${apu1//[^.]/} ]] && { luku1=${apu1:0:18}; kokonaisluku=0 ;} || { luku1=${apu1:0:18}"."; kokonaisluku=1 ;}
[[ ! ${apu2//[^.]/} ]] && desimaaliosa2=''
[[ ${apu2//[^.]/} ]] && { luku2=${apu2:0:18}; kokonaisluku=0 ;} || { luku2=${apu2:0:18}"."; kokonaisluku=$(( 1 & $kokonaisluku )) ;}
desimaaleja=$((${#desimaaliosa1}+${#desimaaliosa2})); $tulosta desimaaliosa1:$desimaaliosa1" desimaaliosa2:"$desimaaliosa2" desimaaleja:"$desimaaleja
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}
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
# echo; echo tulos laskusta: $1 \* $2' . Ylärivi on bc:stä ja alarivi tästä skriptistä:'
# bc<<<"scale=40; $1*$2"
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
echo $merkki${tulos##+(0)} ;}
function potenssi () { # varsinainen potenssiin korottaminen
apu=$( kerro18 $1 $1 )
for (( n=2; n<$2; n++ )); do
apu=$( kerro18 $apu $1)
done
[[ $1 = 0 ]] && { apu=1; [[ $2 = 0 ]] || apu=0 ;} # sopimus on että: 0 ^ 0 = 1 - mutta 0 mihin tahansa muuhun potenssiin on nolla
echo; echo "lasku on: $1^$2"
echo -n "bc:n laskema varmasti oikea tulos: ";bc -l<<<"$1^$2"
echo "tämän skriptin laskema tulos: $apu" ;}
time potenssi 1507.5 5 # tämä on merkintätapa laskulle: 1507.5^5. Jos laskun haluaa tehdä jossain skriptissä niin merkintatapa on: luku=$(1507.5^5).
# luku ei ole desimaaliluku vaikka se siltä näyttääkin vaan se on tekstijono.
time potenssi 2.5 30
time potenssi -2 2
time potenssi -2 3
time potenssi 9.999999 18 # tarkkuus huononee suunnattomasti
time potenssi 0.987654321098 10
Siis skriptiä kokeillaan siten että maalataan sen aivan kaikki lauseet koodista kerralla täältä verkkosivulta ja liimataan ne pc:n päätteeseen ja painetaan return. Vain BASH skriptit toimivat tällätavoin.
- kokeillessa älä haaveilekaan tiedostosta, suoritus oikeudesta tai käskystä !#/bin/bash. Käyttäjiä tylytetään niillä.