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 ... 35
61
- tiedostosta lukeminen tapahtuu näin:
Koodia: [Valitse]
   takaosa "$( < /boot/grub/grub.cfg)" BE # tulostuu: GIN . Tai kun on syytä olettaa että etsittävä löytyy 100:lta ensimmäiseltä riviltä jättikokoista tiedostoa:
   takaosa "$( head -100 ~/tmp)" aa
- eivät nämä BASH-toteutukset vastaa nopeudeltaan läheskään sitä mihin esimerkiksi käsky:grep kykenee eikä näistä luultavasti nopeita saakaan. Eikä ilman kirjastoja näistä saa helppoja käyttääkään. Mutta jos kirjastoja käyttäisi niin grep jäisi monipuolisuudessa toiseksi sillä näistä voi räätälöidä hieman erilaisia versioita vaikka kuinkapaljon - aikaisemmin tuntemattomiinkin tarpeisiin. Tuntuu yhä enemmän siltä että virtuoosit ovat keksineet BASH:ille vaikeuksia ettei se kilpailisi sed:in, awk:in, grep:in ja muiden semmoisten kanssa.       

***

Tai jos funktion halutaan tulostavan kaikki löydetyt niin funktio voidaan tehdä rekursiiviseksi. Näillä 'nopeilla käskyillä' rekursio on nopea joten nopeus-tappio on pieni. Tässä tulostus-suunta on viimeisestä ensimmäiseen mutta pienellä koodimuunnoksella sen saisi muutettua ensimmisestä vimeiseen. Esimerkiksi:
 
Koodia: [Valitse]


function takaosa () { apu="${1##*$2}"; apu=${apu//\"}; echo ${apu%% *}; [[ "${1%$2*}" =~ $2 ]] || return; takaosa \""${1%$2*}"\" "$2"  ;}

takaosa "ma1ma2makolme ma4 ma5
ma6

 ma7
ma8 ma9
ma10" ma

Tulostuu:
10
9
8
7
6
5
4
kolme
2
1

***

Tiedostosta etsittäesssä vain kutsu kirjoitettaisiin eritavalla:
Koodia: [Valitse]
function takaosa () { apu="${1##*$2}"; apu=${apu//\"}; echo ${apu%% *}; [[ "${1%$2*}" =~ $2 ]] || return; takaosa \""${1%$2*}"\" "$2"  ;}
takaosa "$( < /boot/grub/grub.cfg)" B

tulostuu:
 
EGIN
e
EGIN
EGIN
EGIN
EGIN
EGIN
EGIN
EGIN
EGIN
EGIN
EGIN


- etsintä siis huomioi kirjainkoon joten sillä siellä on keran Be
- koetapa:  takaosa "$( < /boot/grub/grub.cfg)" b

62
Oletpa oikeassa, sotkeuduin taas lahkeisiini. Taisi ylirasitus iskeä ja poistun lepäämään ainakin joksikin aikaa. Silti vielä: BASH:hissa on grep:in tai regex:in kaltaista nopeaa hakua, mutta kuitenkin ihan erilaista:
Koodia: [Valitse]

function etuosa () { apu="${1%%$2*}"; echo ${apu##* } ;}        # tulostaa ensimmäisen löydetyn edessäolevan
# tai: function etuosa () { apu="${1%$2*}"; echo ${apu##* } ;}  # tulostaa viimeisen löydetyn edessäolevan
# tai: function etuosa () { apu="${1%$2*}"; echo ${apu#* } ;}   # tulostaa koko välin ensimmäisestä viimeiseen löydettyyn
                   
etuosa "unten siivin luoksesi hiivin
        suutelen silmäsi uneen
                         
        vartiopaikalla suorana seisten
        kirjoitan nimesi lumeen" kal 
     
- siis 'ensimmäisen löydetyn edessäolevan' tapauksessa: kal on se mitä etsitään - tulostuu: vartiopai
- pitää määrätä hakutermi oikein tai saa tyhjää käskystä. Siis määritä haku näin:
  etuosa "keke roosberg syöksyi mutkaan nopeudella 235 km/t" " km/t" # näin löytyy tuo 235 - huomaa välilyönti hakutermin alussa.

--- tai:
Koodia: [Valitse]

function takaosa () { apu="${1##*$2}"; echo ${apu%% *} ;}        # tulostaa ensimmäisen löydetyn perässäolevan
# tai: function takaosa () { apu="${1#*$2}"; echo ${apu%% *} ;}  # tulostaa viimeisen löydetyn perässäolevan
# tai: function takaosa () { apu="${1#*$2}"; echo ${apu% *} ;}   # tulostaa koko välin ensimmäisestä viimeiseen löydettyyn
                                                   
takaosa "kun mä kuolen sadetakkini sä saat
         sillä olen varma että
         pilv1enpääl ei sada vettä
         huh hah hei ja rommia pullo" "ma "
- tulostuu:että . Ja "a " tuostaa:pullo . Ja ma*sa tulostaa:da .  Ja: [0-9][iest] tulostaa:npääl. Kaikki niinkuin pitäisikin.
- huomioi että * ylittää sekä välilyönnit että rivinsiirrotkin.

63
Ihan alkuunsa BASH:ia syytettiin eniten hitaudesta - ja hitaus olikin kiistatonta johtuen hitaista käskyistä - tulkattavasta käskystä tulee hidas jos siihen lisätään runsaasti ominaisuuksia jotka täytyy tulkata tarvitaan niitä tai ei - ja kussakin käytössä tarvitaan vain yhtä ominaisuutta ja muut ovat turhaa painolastia. Koska 'nopeat käskyt' osaavat tehdä vain yhtä asiaa ovat ne nopeita - ja näyttää yhä enemmän siltä että nopeus saattaakin usein olla paljon-paljon parempi kuin on luultu.

Seuraavaksi alettiin mollata BASH:ia kyvyttömäksi - mutta 'nopeilla käskyillä' tehdyt skriptit ovat kyvykkäitä ja helpommin räätälöitävissäkin - joten ne toimivat paremmin ja niinkuin niinkuin etukäteen olettaisi - mutta nykyisistä käskyistä kootut skriptit tekevät yleensä sitä mutta toisinaan tätä - sillä ne ovat loogisia pommeja tarkoituksella tai tahallaan.

Skriptit olisi viisainta kirjoittaa funktiomuotoon - silloin voisi kutsua noita pitkiä skriptejä yhdellä sanalla. Mutta tämän estämiseksi annettiin 'kirjastokieto'?

Kirjastokieltoon voi suhtautua:
- ei välitä
- kirjastot jätetään tekemättä mutta kyllä funktioita saa tehdä. Eikä niitä funktioita tarvitse kirjoittaa vaan kopioida koko roska kerralla - tosin skripteistä tulee niin tehden nopeasti kammottavan kokoisia. Mikä on erittäin vahingollista aloitettaessa ohjelmointi sillä ihminen tajuaa nopeiten sellaisen joka voidaan esittää yhdellä sivulla.

Ainoa järkeväntuntuinen selitys 'kirjastokiellolle' on se että kirjastoissa on jotakin joka on tietoturvariski. Mikä pitää varmasti paikkansa sillä tietokone on tietoturvariski vaikka sen maalaisi vihreäksi ja heittäisi mereen. Selitys semmoisenaan on siis yhtätyhjän kanssa.

***

Luinpa kuinka jättikokoisen tiedoston viimeisen lauseen etsimisessä hehkutettiin komentoa: 'echo tiedosto | tail -1' - että se on melkein voitamaton - awk ja sed mukaanlukien. Olihan se ihan selvä haaste joten tein satunnaisesta tekstistä giga-luokan tiedoston nimeltään: ~/temp - ja sitten skriptin sen viimeisen rivin tulostamiseksi.
- pienen koetiedoston voi tehdä minuutissa käskyllä:
Koodia: [Valitse]
rm ~/temp;touch ~/temp; for n in {1..10000}; do echo $n'     '$(cat /dev/urandom | base64 | head -c 100) >> ~/temp; done

Toimintanopeuksia tosi-suurella tiedostolla:
Koodia: [Valitse]

cat ~/temp | tail -1                                                                                           #  kesti: yli minuutin
truncate -s-1 ~/temp; echo -n "¤" >> ~/temp; a=$(timeout .001 tac ~/temp); echo ${a%%¤*} ; truncate -s-1 ~/temp #  kesti 10 ms


Mutta täytyy myöntää ettei skriptin toiminta ole moitteetonta sillä jokainen käyttökerta lisää viimeisen rivin loppun jotakin vaikka näennäisesti kaikki lisätty poistetaan eikä editorissakaan näy mitään - ilmeisesti olisi toinen viikkojen pituinen etsintä löytää käsky jolla se lisätty poistetaan.

***

Olen aina inhonnut sitä kun jostain asiasta väitetään ettei BASH osaa sitä - sillä en ole löytänyt yhtään sellaista ongelmaa johon ei löydy aikanaan ratkaisua - ihan toinen asia on että ratkaisua joutuu useinkin etsimään kuukausia - ratkaiseminen on niinkuin palapeli johon löytyy palasia kauan - ja vaikka asiaa ei tietoisesti edes ajattele niin palapeli lymyää jossain aivonperukoissa - ja sitten omia aikojaan ratkaisu selviää kuin itsestään. Tosin siitä löytämisestä ei ole mitään varsinaista hyötyä sillä BASH on päästetty niin pahaan kuntoon ettei sitä korjaa koskaan enää mikään määrä virtuooseja - mutta saahan onnistumisesta sentään paljon mielihyvää.

Mutta noita palapelin osia ei kukaan pysty kokoamaan pelkästään omista tiedoistaan sillä BASH on niin laaja ettei edes kaikkia sen käskyjäkään kukaan voi hallita täysin: esimerkiksi tuon äskeisen käskyryhmän palasia kokosin netistä vuosia sillä siinä on kolme 'ratkaistavaksi mahdotonta' ongelmaa - ja ilmeisesti siis neljäs pitäisi vielä löytää. Muuten useimmat löytyneet palaset olivat jopa yli parikymmentä vuotta vanhoja - ei oikeastaan ihme sillä silloin käyttäjät alkoivat paeta BASH:ista.

64
Yritän jatkuvasti saada jotain järkeä siihen miksi virtuoosit ovat tehneet BASH:ista surkimuksen - selitän tätä siis itsellenikin: on pakko miettiä asiat paljon paremmin kun asiat kirjoittaa.

BASH:in ylipapit alkoivat jo yli kolme vuosikymmentä sitten toivoa että BASH hiljokseen häviää - syytä haluun en tiedä, mutta tosiasia on että BASH on niin monipuolinen ettei yksikään professori kykene hallitsemaan siitä isompaa osaakaan täydellisesti vaan oppilaat hyppivät silmille kokoajan - onhan se käytännössä pätevä syy yrittää päästä siitä eroon.

Mutta BASH oli siihenaikaan niin voimissaan ettei sen tuhoamisesta olisi tullut mitään - mutta mistähyvänsä pääsee eroon kun vaan antaa sen päättäjille riittävän löysän talutushihan niin he varmasti hirttävät siihen itsensä - ja tässätapauksessa siinä sivussa meinisi myös BASH. Hullulta tuntuva konsti toimii aina vaikkakin se on kamalan hidas - kaikkialla muuallakin käytetään tuota samaa keinoa  - sillä ei ole mitään sellaista joka ei hyödyttäisi jotakuta ja lähettäisi toiset manalaan.

Aluksi virtuoosit alkoivat sanoa että BASH on surkean hidas kaikssa muussa kuin tietokoneen hoitamisessa - mikä on täysin totta. Sitten pienen säätelyn jälkeen aletiin sanoa että useissa asioissa BASH on myös liian omituinen - sekin ihan totta. Virtoosit puhuvatkin aina varmasti totta joten esimerkiksi voi luottaa siihen että 'advanced guidet' puhuvat BASH:in alkeista ihan asiaa - pidemmäälle nuo oppat eivät edes yritä mennä huolimatta nimestään. Mutta ne esimerkiksi vihjailevat ettei BASH osaa käyttää nimiparamereja funktioissa - mikä on sekin yksi ohjelmointikielen menestymisen ehdoista. Ehkä ne itsekin uskovat väitteiseensä.

Tavallaan väite pitää kyllä paikkansa koska nimiparametrit eivät toimi BASH:issa samallatavoin kuin muissa kielissä - sillä kun BASH:issa passaat funktioon muuttujan käyttäen sen nimeä niin funktiossa se on tosiaan pelkkä nimi - kunnes se sidotaan saman-nimisen muuttujan arvoihin ja tyyppimääreisiin - tyyppimääreitä ovat sen arvot ja onko se tavallinen muuttuja vaiko tavallinen- tai assosiatiivinen matriisi - ja lisäksi niiden tarkenteet. Sitominen tapahtuu seuraavalla tavalla:

BASH pitää kirjaa muuttujistaan ja niiden senhetkisistä arvoista niinkuin muutkin kielet - ja BASH antaa kirjanpitonsa skriptaajan käyttöön käskyllä: declare. Kun kirjanpidosta löytyy sama muuttujanimi niin siinähän se muuttuja on argumentteineen ja kun määritelmät kopioi sieltä niin muuttuja on täysin kunnollinen - mutta jos jättää tämän sitomisen tekemättä niin nimiparametrit eivät toimi. Sitomiseen on muuttujakuvausten käyttämisen lisäksi pari käskyäkin: let ja read - jos oletetaan ettei käskyä: eval käytetä. Voiko olla sattumaa että kolme käskyä ja yksi menetelmä toimivat oikein asiassa johon niitä ei ole tarkoitettu? Mahdollisuus on pienempi kuin saada lotossa 10 oikein - kyllä ne on aikoinaan tarkoituksella tehty ja nimiparametreja osattu käyttää.

Samalla ratkeaa sekin ettei parametreja tarvitse palauttaa sillä jokakrran muuttujaa käytettäessä muuttujakuvausta muutetaan vastaamaan tilannetta - ja muuttujakuvaus on globaali elikkä sama kuvaus on käytössä kaikkialla - niin pääohjelmassa kuin jokaisessa funktiossakin - joten jos sitä muutetaan jossakin niin se muuttuu kaikkialla.

- senkin virtuoosit tekivät että he 'kielsivät' käyttämästä käskyä: eval - se nimittäin on helpoin ratkaisu myös yhteensitomiseen. Ja taas välittämättä virtuoosien puheista BASH:in kehittäjät ovat laittaneet tiedostoon: ~/.bashrc noin kolme eval-käskyä. Ketä uskoa?

Koetan selittää funktio-parametrien palautusta toisellakin tavalla: funktoista ei ole täyttä hyötyä elleivät funktiosta palattua sinne lähetetyt muuttujat ole muuttuneet. Tähän muuttamiseen on kaksi keinoa: muuttoksen suorittaa joko funktion kutsuja saatuaan tulokset funktiosta takaisin tai funktio itse toimintansa yhteydessä. Muissa kielisssä muutoksen tekee funktion kutsuja ja sitävartenhan parametrien arvot tulee palauttaa sieltä funktiosta. Mutta BASH ei toimi näin möllillä tavalla vaan muutoksen tekee funktio itse - tämä edellyttää että käytetään nimiparametreja ja että kielessä on kyky yhdistää nimi sen arvoon - BASH:illa on tuo kyky mutta muilla kielillä ei - mutta kyky ei riitä ellei sitä myös käytetä ja virtuoosit jättävät kylmän tyynesti kyvyn käyttämättä. Kun virtuoosit sanovat että BASH ei osaa parametreja palauttaa niin he puhuvat siis ihan totta - eikähän mikään laki vaadi kertomaan ettei BASH:issa parametreja tarvitsekaan palauttaa - mikäli on toimittu oikein. 

---

Ja 'nopeat komennot' nopeuttavat toimintaa paljon - nopeuttivathan ne aikanaankin mutta niiden käyttäminen romahti koska 'nopeat komennot' ovat nopeita vain kun skriptissä melkein kaikki käskyt ovat niitä nopeita eikä ilmeisesti kukaan huomannut sitä.

Toistaiseksi on liian aikaista edes arvella kuinka paljon ne nopeuttavat kokonaisuutta, mutta jo tähänasti tehdyt 'nopeita komentoja' hyödyntävät funktiot tekevät melkoisen kyky-loikan yksittäisissä tapauksissa - ja tekevät ainakin sen että BASH on surkea enää vain vähän. Ja on täysin varmaa että paljon lisää löytyy.

Jotta uusia ominaisuuksia voisi käyttää järkevällä tavalla täytyy niiden käyttämiseksi kirjoittaa funktio joka täytyy voida sijoittaa kirjastoon - mutta  virtuoosithan 'kielsivät' kirjastot? Ja taas BASH:in kehittäjät ovat asentaneet jokaiseen koneeseen itselleen merkittävistä toimista kirjastollisen funktioita - välittämättä virtuoosien 'kirjasto-kiellosta'. Ja eikö kehittäjä ole verrattomasti tietävämpi kuin virtuoosi?
- voit itsekin käyttää noita super-omituisia funktioita - esimerkiksi funktio nimeltä: quote palauttaa sille lähetetyn sanan lainausmerkeissä. Käske: quote antti -> tulostaa 'antti'

Ja BASH:ista yritetään muinkin keinoin tehdä surkimusta: esimerkiksi 'nopeat komennot' haudattiin uusien käskyjen alle ettei kukaan vaan huomaisi niiden käyttökelpoisuutta - ja muut keinot haudattiin muun roskan alle - samaa keinoa käytetään kaikkialla: hyväkin menetelmä hukkuu kun päälle sataa tarpeeksi roskaa. Toinen konsti on vaieta asia kuoliaaksi - eihän kukaan usko mihinkään sellaiseen josta virtuoosit eivät koskaan puhu.

Teoriassa BASH kykeneekin samoihin ohjelmointitöihin kuin muutkin kielet - hidashan BASH on eikä siitä taida nopeaa saadakaan - mutta pikkuinen toivonkipinä siitäkin on. Varsin omituinenkin BASH on. Käytännössä BASH on melkolailla toivoton kieli - mikä johtuu virtuoosien määräämistä rajoituksista joilla ei tunnu olevan kunnon syytä - mutta jos niistä ei välitä niin taivas tuntuu olevan rajana. Mutta 'kirjastokiellosta' ei oikein pääse eroon: kirjastot toimivat kyllä ihan hyvin, mutta 'kirjastokielto' estää kirjastojen lisäämisen perus-asennukseen - ja kirjastojen puute tuhoaa kielen ajanoloon takuvarmasti - ihme että BASH sinnittelee edelleen. Silti netistä löytyy vielä ainakin kymmenen isoa BASH-kirjastoa - unohtuneet nettiin ajalta jolloin kirjasto-osoitin vielä toimi?

Ja tuloksena tästä 'kirjasto kiellosta' on että olemattoman pieniinkin ongelmiin tarkoitetut skriptit ovat tuhatrivisiä hirviöitä jonka lauseet koostuvat sanoista jotka ovat todella inhottavia kirjoitus-asultaan joten niitä ei edes muista vaan ne täytyy jokakerran hakea itsetehdyistä tiedostoista.
 
Omaan koneeseensa voi toki laittaa itsetehdyn kirjaston - minun koneessani iso kirjastoni on toiminut moitteettomasti jo yli kahdeksan vuotta - vaikka koneeni ovat vaihtuneet usein ja myös Ubuntun versiot vaihtunet parikertaa vuodessa.

65
'nopeat käskyt' ovat paitsi nopeita niin niillä myös helppo määritellä nopeita ja monipuolisia toimintoja sillä ne eivät ole loogisia pommeja niinkuin uudet käskyt helposti ovat. Tosin vanha käskyt ovat vaikeasti muistettavaa merkki-sotkua. Esimerkiksi kun pyydetään:

kerro millä paikalla etsitty on hakulausekkeessa kun jossain haettavan jälkeen täytyy olla määrättävä sana:
Koodia: [Valitse]

merkkijono='abc def ghi jkl'; trigger=bc; arm=gh; etuosa="${merkkijono%%$arm*}";apu=${#etuosa}; [[ "${merkkijono:0:$apu}" =~ $trigger ]] && etuosa="${etuosa%%$trigger*}" && echo 'merkkino='$((${#etuosa}+1))
- tätä toimintoa varten on tehty myös regex - ja joku on puurtanut kuukausitolkkua kehittäessään sitä. Tämä tekemisessä meni parituntia - olihan tämä ensimmäinen laatuaan.
- kyse on aikaisemmin tuntemattomasta toiminnosta joten esimerkki on todennäköisesti huonosti valittu - mutta kannnattaa esittää sillä tiedähäntä mihin tämä johtaa.
- esimerkiksi tiedon etsimiseen rekistereistä tämäntapaisia tarvitaan - mutta BASH  on saanut niin huonon maineen hitaudestaan ja 'kiikkerästä' toiminnastaan etteivät luotettavasti toimivat 'nopeat käskyt' siihen minkäänlaista muutosta tuo.   


Tai sama haku hieman erilaisilla määrittelyillä: kerro millä paikalla etsitty on hakulausekkeessa kun haettavaa ennen täytyy jossakin olla määrättävä sana:
Koodia: [Valitse]

merkkijono='abc def ghi jkl'; trigger=bc; arm=gh; etuosa="${merkkijono%%$arm*}";apu=${#etuosa}; [[ "${merkkijono:$apu}" =~ $trigger ]] && etuosa="${etuosa%%$trigger*}" && echo 'merkkino='$((${#etuosa}+1))

- monipuolisemmillakaan hauilla nopeus ei juurikaan muutu.

66
Kirjastojen puutteen takia BASH:issa on runsaasti seuraavankaltaista: haluttaessa erottaa tekstistä joku sana käytetään yleensä seuraavantyyppistä toimintaa :
Koodia: [Valitse]
echo $text |  awk '{ print $3 }'       # BASH:in vastaava toiminto on:
matriisi=($text); echo ${matriisi[2]}  # ja se toimii kymmenkunta kertaa nopeammin. Onhan koodi vähän pidempi eikä ihan yleisesti
                                       # tunnettukaan. Mutta silti ihmettelen miksei sitä käytetä sillä virtuoosien on pakko
                                       # tuntea tuommoiset. Halutaanko vähätellä BASH:ia vaiko edistää awk:ia?
                                       # Mutta entäpä jos BASH:issa olisikin kirjasto ja siellä funktio:
                                       # function write () { matriisi=($1); echo ${matriisi[$2]} ;}. Silloin käsky olisikin:
write "$text" 2                        # Ja toiminto olisi edelleen melkein yhtä nopeaa.

-  käske: 
Koodia: [Valitse]
function write () { matriisi=($1); echo ${matriisi[$2]} ;}
käskystä jää sllähetkellä avattuna olevan päätteen muistin funktio - aivan niinkuin funktio olisi haettu kirjastosta. Käske sitten samassa pätteessä:
Koodia: [Valitse]
write "yksi lensi yli käenpesän" 2
-> tulostuu: yli .

- kyse on ajoista 4ms ja 0.5ms. Mutta ajetaanpa toiminto kuinka monesti tahansa samassa skriptissä niin toiminta kestää jokakerran melkein yhtäkauan. Ja kun samanlaisia pikkujuttuja on paljon niin koko BASH alkaa tahmaamaan. 
- voi käskeä nyös: set yksi lensi yli käenpesän; echo $3    # se on lyhyt ja nopea - niinkuin BASH taitaa olla aina jos ei hölmöillä.

67

Tai muunnos tieteellisestä esitysmuodosta normaaliin esitysmuotoon: 
Koodia: [Valitse]
function from_sci ()
{ nollia=00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
kokonaisosa=${1%%.*} ; kokonaisia=${#kokonaisosa}
luku=${1//[.]/}
luku=${luku%%e*}
[[ ${luku:0:1} = - ]]  && { merkki=-; luku=${luku:1} ;} || merkki=''
exp=${1##*e}
 (( $exp > 0 )) && { luku=$luku$nollia; apu=$merkki${luku:0:$(($exp+$kokonaisia))}.${luku:$(($exp+$kokonaisia))};apu=${apu%%+(0)}; echo ${apu%.} ;} || { [[ -exp -gt $kokonaisia ]] && echo $merkki.${nollia:0:$((-$exp-$kokonaisia))}$luku || echo ${luku:0:$(($kokonaisia+exp))}.${luku:$(($kokonaisia+exp))} ;} ;}

# testausta ja suoritusajan määritystä  - yksi muunnos kestää luokaa 0.25 ms
time for expo in {-55..55}; do from_sci 123456789012345678901234567890123456890.12345678901234567901234567890123456789e$expo; done

68
Printf-käsky:llä on käynnissä ikuinen taistelu desimaalipisteen kanssa ja sillä lukujen koko rajoittuu 18 merkkiin. Kummankin puutteen voi poistaa 'nopeilla käskyillä' tehdyllä skrpteillä jotka osavat tulostaa  normaalissa esitysmuodossa annetun luvun tietellisessä esitysmuodossa tai päinvastoin - olipa luvuissa numeroita vaikka satoja - eikä desimaalipisteestä ole harmia:
Koodia: [Valitse]
function to_sci () { luku=$1
[[ ${luku:0:1} = - ]]  && { merkki=-; luku=${luku:1} ;} || { merkki=''; luku=${luku//+/} ;}
luku=${luku##+(0)}  # poistetaan etunollat - nimenomaan 0.xyx -> .xyz
apu=${luku%%.*}     # erotetaan kokonaiosa
exp=${#apu}         # lasketaan kokosasta positiivinen exponentti
[[ ${luku:0:1} = \[.,] ]] && { # katsotaan onko ensimmäin merkki desimaalipiste - silloin exponentti on negatiivinen
apu=${luku%%[1-9]*} # poistetaan kaikki siitälähtien kun ensimmäinen numero tulee desimaalipisteen jälkeisen nollasarjan perään. Siis tulos on desimaaipiste yksin jos nollia ei ole yhtään.
exp=$((1-${#apu}))  # lasketaan negatiivinen exponentti nollien lukumäärästä vähentäen yhden desimaalipisteestä
luku=${luku:1}      # palautetaan desimaaliosa jättäen desimaalipite pois
luku=${luku##+(0)}0 # poistetaa etunollat ja lisätään yksi peränolla
echo $merkki${luku:0:1}.${luku:1}e$(($exp-1)) # tulostetaan näin kun expnentti on negatiivinen - kokonaisosa on nyt ensimmäinen numero
} || { # halu dokumentoida aihuttaaa sen että ehtojen jälkeinen ryhmitys tulee rikkoa erotamalla nämä merkit tällätavoin
luku=${luku//[.,]/} # poistetaan desimaalipista sotkemasta
echo $merkki${luku:0:1}.${luku:1}0e$(($exp-1)) # tulostetaan näin kun exponentti on positiivinen - kokonaisosa on nyt ensimmäinen numero
} ;}

to_sci 1234567890123567890123456789012345678901234567890,1234567890111111111111111111111111111111111111111111111111111111111111111111
to_sci 1
to_sci +1
to_sci -1
to_sci .1
to_sci 0.1
to_sci -0.1
to_sci 10


- jos myöhemmin huomaa että muunnos toimii joskus väärin niin sehän olisi vain hyvä - joutuisi nimittäin korjaamaan ja ajukopan ränsistymistähän tässä koetetaan hidastaa.
- BASH:ia ei voi dokumentoidakaan kuten muita kieliä sillä ehtojen toinen tai kummatkin haarat saattavat olla koottuja lauseita ja mikäli ne kommentoi ninkuin normaalisti niin rakenne särkyy. Ei se kommentointi kuitenkaan raketti-tiedettä ole.
- BASH toteuttaa ilmaukset oikein silloinkin kun voisi vannoa ettei semmoista sotkua mikään pysty tulkitsemaan - joten skriptit lyhenevät ja nopeutuvat ikuisesti - tai onhan nollaa vaikeaa enää pienentää.
- nopeat käskyt' tosiaan ovat nopeita: näin monirivinen skripti on yhtänopea kuin yksi uusi käsky.

Muunnoksen kestoajan (noin .3 millisekuntia) määrittäminen:
alkuhetki=$(date +%s%N); to_sci 1234567890123567890123456789012345678901234567890.1234567890111111111111111111111111111111111111111111111111111111111111111111 ; loppuhetki=$(date +%s%N); aika=$((10#$loppuhetki-10#$alkuhetki-1000000)); apu=000000000$aika; apu=${apu: -14: -9}.${apu: -9}; echo ${apu##+(0)}
- printf "%e" 100000 kestää noin .2 millisekuntia

69
 
Käsitelläänpä sitten bc:

Kerro18 on huomattavasti nopeampi kuin bc - tosin kerro18 on vain 36 numeroinen liukuvanpilkun kertolasku. Samoin 72 numeron desimaalilukujen yhteenlasku on sekin nopeampi. Desmaalilukujen jakolaskukin toimii sekin nopeasti antaen noin 50 numeroa jotka yleensä kaikki oikein - valitettavasti eivät aina. Mutta sittenkin bc on ehdottomasti kunkku - nämä ovat vain vähäisiä säröjä bc:ssä.

Mutta esimerkiksi lukuja pyöristtessä bc on toistaitoinen: bc ei oikeastaan osaa pyöristää ollenkaan vaan se ainoastaan leikkaa sen desimalien lukumäärän määrämittaan. Ja sen leikkaamisenkin suorittamiseksi sen täyy tehdä jakolasku - se lasku voi kylläkin olla valelasku jossa jaetaan luvulla 1 joten mikään ei muutu. Mutta ihmiset eivät yleensä tiedä tätä eivätkä saa aina edes leikattua - ja taas saadaan lisää syytä mollata BASH:ia. Ja leikkaamiseenkin bc kuluttaa ylettömästi aikaa.

Mutta pyöristys-skripti pyöristää aina oikein ja on lisäksi salaman-nopea, 'kymmenenkertaa' nopeampi kuin bc:
Koodia: [Valitse]
function pyöristä () {
[[ ${1:0:1} = - ]]  && merkki=- || merkki=+
[[ $1 =~ \. ]] &&  desimaaliosa=${1##*.}000000000000000000 || desimaaliosa=0000000000000000000 # onko desimaalipistettä ...
kokonaisosa=${1%%.*}
apu=${desimaaliosa:0:$2}
desimaaliosa=$(($apu$merkki(( ${desimaaliosa:$2:1} >= 5 )) ))
echo $kokonaisosa.$desimaaliosa ;}

time pyöristä 1.22634 2          # tämä 'nopeilla käskyillä' toteutettu BASH-skripti kestää 0ms   
time bc<<<"scale=2; 1.22634/1"   # tämä bc:n lasku kestää 4ms - ja tulos on hieman virheelinen.
 
- tarkistelepa lausetta: $(($apu$merkki(( ${desimaaliosa:$2:1} >= 5 )) ))  -> BASH kokoaa ensin sellaisen tekstijonon kuin käsky määrää. Lähetettäessä tekstijono matematiikka-moottoriin sen osista jokainen määritellään sentyppisesti kuin hyvä tulee: siis jokainen osa voi olla tarpeen mukaan joko: tekstijono, numero, operaattori, laskutoimitus, funktiokutsu tai looginen arvo 0 tai 1 -> kaikki mitä sinne matematikka-moottoriin lähetetäänkin tuntuu toimivan.
- skandit funktion nimessä toimivat hyvin. Ei kyllä taida olla järkevää käyttää nimessä skandeja - mutta kokeillaan tämänkerran.

70
Sed selviytyy paljon paremmin: muutettaessa tekstistä annettu sana kaikilla toiseksi kuluu  kymmenen kilosanan tiedoston käsittelyyn aikaa noin millisekunti niinkuin sed:illäkin. Pienemmillä tiedostoilla BASH on jopa 4*nopeampi ja isommilla jopa 4*hitaampi.
 
Koodia: [Valitse]
apu=$(cat /dev/urandom | base64 | head -c 10000); time printf "%s" ${apu[@]//a/alku}     # apu:sta muodostetaan ensin 10000 sanan tiedosto.  Tämä on pelkkää BASH:ia
apu=$(cat /dev/urandom | base64 | head -c 10000); time echo ${apu[@]} | sed 's/a/alku/g' # tämä taas käyttää apunaan sed:iä

# toteutustapoja on muitakin, esimerkiksi:
< /boot/grub/grub.cfg readarray doku; time { echo "${doku[@]}" | sed 's/a/alku/g' ;}

- myös sed on niin suuri ettei sen toimintojen merkittävää osaakaan saa tutkittua ihmisiässä.
- myös pieniä sed-skripteja on tolkuton määrä - ja myös ne tekevät pieniä ihmeitä. 

71
Awk, sed ja bc ovat olleet tähänasti täysin ehdottomia nopeus-kuninkaita. Nyt ne ovat tosiaan menettämässä asemaansa 'BASH:in nopeille käskyille' - näin alkuunsa vasta muutamassa pienessä tehtävässä. Mutta esimerkiksi awk häviää taiston pahimmillaan 'kymmenkertaisesti'. Eivät ne tosin saa tuommoisia rökäletappiota kuin pienissä asioissa ja todella suurissa ne ovat nopeampiakin - myöntääköhän Linux niille enemmän resursseja? Eikä tosiaan kaikkia ziljoonia awk- ja sed-skriptejä kerkiä edes ajatella korvaavansa - mutta BASH varmasti osaisi. Tarkempi kuvaus siiä kuinka asiat tehdään:

Awk:ia, sed:iä ja bc:tä käytetään BASH:issa putkittamalla tutkittava niihin. BASH käyttää putkitusta vain kutsuessaan toisia skriptikieliä mutta kutsuessaan toisia BASH-skriptejä se käyttää mieluummin funktioita. Koska heti BASH-opetuksen alussa opetetaan käyttämään putkitusta mutta funktioista ei puhuta juuri sen enempää kuin että väitetään ettei BASH osaa funktioita kunnolla käyttää - joten eihän funktioiden käyttöäkään osata tekemisestä puhumattakaan. Esimerkki  awk:in käytetyimmästä toiminnosta:
Koodia: [Valitse]

# tämä on tosin ihan pieni mutta yleisin awk skripti:
echo  "10 20 30 40 50 60" | awk '{print $3}'

# näin se korvataan BASH:in 'nopealla käskyillä':
function awk2 () { echo ${@:$(($1+1)):1} ;}; awk2 3 10 20 30 40 50 60 

# tai jos heittomerkejä ehdottomasti haluaa käyttää: function awk2 () { apu=$1; set $2; echo ${@:$apu:1} ;}; awk2 3 "10 20 30 40 50 60"
# tai jos valittavissa on välilyöntejä niin: function awk2 () { echo ${@:$(($1+1)):1} ;}; awk2 3 'nyt saat kokea, koito' 'kokea kovaosainen' 'apen luista leukaluuta, anopin kivistä kieltä'
- BASH:in käyttämä tapa on tosin paljon hankalamman näköinen - mutta vain koska BASH:issa funktio-kirjastot on "kielletty" - muuten koko awk on sekin tavallaan kirjasto joten tilanne on epäreilu. Mutta jokatapauksessa BASH on kymmenkunta kertaa nopeampi - pärjääköhäm Pythonkaan taistossa hautaan jo saatetun BASH:in kanssa tässä pikkujutussa?
- awk on suurissa matemaattisissa toimissa paljon parempi kuin BASH. Eikä toistaiseksi voi edes aavistella kuinka noissa isoissa matemaattisissa laskuissa käy - ja onnistuvatko ne edes.
- awk:ssa on myös sadoittain pieniä muutaman merkin käskyjä jotka suorittavat pieniä ihmeitä - mutta eihän niistäkään misään kunnon yhteenvetoa ole.
- alkaa tuntua siltä että suhmurointia on liikaa ja käry on varmasti käynyt aikaisemminkin. Onko professorien valta niin ehdoton että asiat voidaan yksinkertaisesti vaieta kuoliaaksi?

72
Yritän tässä määritellä niitä mitä olen kutsunut vanhoiksi käskyiksi sillä eiväthän ne kirjaimellisesti ottaen käskyjä ole vaan dataan lisättäviä ohjeita siitä kuinka data tulisi esittää. Nopeilla käskyillä ei muuten ole matematiikan kanssa mitään tekemistä - silkkaa tekstikäsittelyä ne ovat.

Niiden nopeus tulee siitä että niiden toteuttamiseen tarvitaan vain merkki = tai käsky echo tai jotain senkaltaista nopeaa. Jos pelkästään tuloksia takastellaan niin datan muokkaamiseksi samallatavoin käytetään ohjelmina yleensä sed:iä, pieneltä osin myös awk:ia ja aivan mittömältä osaltaan myös bc:tä - mutta niillä tuloksen saaminen kestää niin kauan ettei niitä käyttäen saisi skripteistä nopeita. Ihmettelin aikanaan kun eräs virtuoosi sanoi että nopeutta haluttaessa pitää unohtaa sed, awk ja bc - täytyy siis olla niin että nopeiden skriptien olemassaolo on tiedetty aina?

Mutta enää en ihmettele sillä nuo vanhat käskyt suorittavat samantapaisia tehtäviä - kylläkin rajoitetummin mutta myös paljon nopeammin. Mutta jokatapauksessa ainoastaan sed, awk ja bc mielletään käyttökelpoisiksi - mikä suureltaosin taitaa johtua siitä etteivät virtuoosit ole koskaan tehneet noista BASH:in nopeista käskyistä minkäänlaista yhteenvetoa - sillä nopeiden käskyjen hyödyllisyys tulee täysin esiin vasta kun melkein kaikki käskyt skriptissä ovat noita vanhoja - sillä tulkki tulkkaa kerralla niitä isonkin joukon eikä tulkkia siis tarvita vähänväliä - joten joskus ne ovatkin parhaita.

Nopeat käskyt ovat vähän niinkuin BASH:in assembleria. Ja vaikka ne ovatkin ylivertaisia suorituskyvyltään niin luultavasti ne on hylätty samasta syystä kuin varsinainen assemblerkin : ohjelman tekeminen ja ylläpito niitä käyttäen on ihan liian kallista - rahallisesti jos skriptin ostaa ja jos skriptin tekee niin selkänahka paukkuu kauan. Virtuooseilla on siis ollut syytäkin tekoseensa - heitä on vaan on niin sopivaa mollata sillä onhan heillä leveät hartiat.

Toki nopeat käskyt toimivat vain niin hyvin kuin moitteettomat käskyt toimivat silloin kolmekymmentä vuotta siten. Esimerkiksi kun etsittävän alku on teksissä eri rivillä kun loppu niin plörinäksi menee. Samoin kirjoitusvirheitä ei suvaita ollenkaan.

***

- lukuja, tekstijonoja, matriiseja tai matriisien yksittäisiä jäseniä voidaan käsitellä 'äärettömän' monin eritavoin - on tosiaan mahdotonta muistaa käsittely-säännöistä pientä osaakaan vaan tiedostoista ne on haettava - eikä missään ole minkäänlaista yhteenvetoa vaan itse se on tehtävä - ja koska yhteenvedon tekeminen on liian suuri työ niin tuloksena on hutilointia ja sotkua - sillä yhteenvedon tekeminen kun olisi isollekin joukolle vaativa tehtävä. Ja nyt yhteenvedon tekeminen on hieman liian myöhäistä sillä ne vanhat käskythän on osin jo unohdettu - joten virtuoosit yrittävät tuhota BASH:in ennenkuin kukaan huomaa?

- nopeat käskyt toimivat vain yhden sanan alueella - mutta lauseesta tulee tavallaan yksi sana kun sen laittaa heittomerkkien väliin - tai myös muuttujalla nimeltään IFS voidaan määrätä sananväliksi merkki jota ei ole käytetty misssään jolloin monirivinenkin teksti tai tiedosto on yksi sana. Jokatapauksessa vanhojen käskyjen käyttämiseen ei tarvita jotakin grep-käskyn kaltaista järkälettä - kuten regex:ät tarvitsevat - joten ne ovat satoja kertoja nopeampia yhden sanan kanssa - tosin ne ovat myös satoja kertoja tehottomampia monirivisten tekstien kanssa sillä IFS:n kanssa leikkiminen on yleensä liian vaarallista - mutta oikeassa paikassa käytettynä ainoastaan nopeus merkitsee.

- BASH:issa jokaista käskyä käsitellään kymmenellä tavalla ennenkuin se lähetetään tulkattavaksi ja päteehän se nopeisiinkin käskyihin. Käsittelyjen suoritusjärjestys on päätetty etukäteen joten kaikki tehdään 'ennen tai jälkeen' monen muun tehtävän joten monet määreet kuten esimerkiksi IFS (= se määrää mikä merkki jakaa sanoja) muuttavat myöhemmin tulevat käsitteyt joskus ihan nurinkurisiksi. Tämmöinen  liian suuri monipuolisuus on myös painolasti kielen käytölle - BASH onkin turhan monipuolinen varsinaiseen ohjelmointiin - prottotyyppien tekemiseen BASH on tosin ihan kiva - ja alzheimerin välttelyssä BASH on erinomainen.

- voi olla varma että tästä kuvauksesta on moni merkittävä ominaisuus jäänyt pois sillä BASH:in kirous on se että kaikesta löytyy loppumattomiin uusia ominaisuuksia joten esimerkiksi kaikki käsikirjat ovat kelvottoman isoja tiiliskiven kokoisia raamattuja - esimerkiksi pääte-ohjelman käsikirja vaatisi viikkojen opiskelun ennenkuin homma olisi hanskassa - ja silti useimmat meistä käytävät päätettä käsikirjaa lukematta ja hyvin se sujuu niinkin - toiminta on silloin tosin joskus hidasta ja ärsyttävää mutta kaikki toimii kyllä jotenkin.

Jospa joskus tajuaisin päivittää tätäkin kuvausta - tai sitten ei, jottei tästäkin tulisi tiiliskiveä - sillä tiiliskiveä ei lue kukaan.

BASH on ainakin mielenkiintoista moniulotteista shakkia eikä hölmöilystäkään tule paineita - sillä onhan BASH siinä itsekin ihan omaa luokkaansa. Mutta jos saisi kirjastojen käyttämisen palautettua niin kuka tietää mitä BASH:ista tulisi.

***

Usein isoilla ja hitailla skripteillä on nopeat korvaajat - muihin kieliin verrattuna BASH on tosin hidas sittenkin. Koska ne on aikaisemmin tunnettu niin miksi ne on hylätty? Sillä ne ovat nopeampia, lyhyempiä ja ainakin tuntuvat vakaammiltakin.

Ehkäpä paras keino kuvata noita nopeita käskyjä on muutama esimerkki toimivista ohje-tyypeistä. Kaikki toimivat niin monella eritavalla ettei esimerkkejä kaikista voi antaa:

- tehtävä1: jaa annettu sana jonkun merkin kohdalta ja esitä joko etu- tai jälkiosa. Jakopiste voi olla myös sana - siis myöhemmin tehty uusissa käskyisä oleva 'parempi' cut-käsky joka jakaa vain yksittäisen merkkin ollessa jakopisteenä on varsinainen möhläys. Jakava merkki voi olla sanassa ensimmäinen jakavan merkin kaltainen tai sitten viimeinen. Jakava merkki voidaan esittää myös merkkiluokan osana, esimerkiksi: [1-9] -> siis nollan kohdalla ei tehdä mitään.
Esimerkiksi: desimaaliosa=${luku##*.} . Tai voidaan kirjoittaa pisteen sijaan: [.,] jolloin desimaalipisteenä voi olla joko piste tai pilkku - nopeudessa tuommoiset pikkujutut eivät tunnu vaikka niitä olisi montakin.

- tehtävä2: tuhoa, muuta tai tulosta sanasta jotakin määriteltävien ominaisuuksien perusteella. Esimerkiksi kun tekstijonona esitetystä desimaailuvusta pitäisi tehdä 'silmälle miellyttävä': poista kokonaisosasta etunollat ja desimaaaliosasta takanollat ja desimaalipistekin jos se jää viimeiseksi - eikä muihin nolliin saa kajota:
Koodia: [Valitse]
luku=${luku##+(0)}; [[ ${luku//[^.]/} ]] && luku=${luku%%+(0)}; echo ${luku%.}
Tällainenkin toiminta voidaan toteuttaa merkkiluokkien avulla - merkkiluokka on esimerkiksi: [0-9] elikä numroiden luokka - tai onhan noita: [:alnum:], [:alpha:], [:blank:], [:cntrl:], [:digit:],  [:graph:], [:lower:], [:print:], [:punct:], [:space:], [:upper:] ja [:xdigit:] - tai jotain muuta hakasulkujen välissä olevaa määritystä - kaksois-sulkuja kulemma kannattaa käyttää.

- tehtävä3. muuta joku sana/sanaryhmä jossain tiedostossa. Muutettavien sanojen ei tarvitse olla yhtäpitkiä - voit siis vaikka tuhota määrättävän sanan koko tiedostossa. Muutettavassa sanassa saa olla välilyöntejäkin. Muutettavia saa olla montakin samalla rivillä.
Koodia: [Valitse]
mika=BEGIN; miksi=ALOITA; < /boot/grub/grub.cfg readarray doku; printf "%s" "${doku[@]//"$mika"/"$miksi"}" | tee ~/poista_luettuasi
tulos ilmestyy näytölle ja myös kotikansion tiedostoon: ~/poista_luettuasi. Mitään ei varsinaisesti editoida - vaaraton esimerkki tämä on.

- tehtävä4: määrittele missä kohtaa annettavassa pitkässä sanassa on jokin kirjain/kirjainryhmä (= pos-funktio). Sekä etsittävässä ettäetsijässä saa olla välilyöntejä. Esimerkki:
Koodia: [Valitse]
isosana="Pitkät on puut Pisan mäellä, hongat Hornankalliolla"; pikkusana="Pisan m"; [[ "$isosana" =~ "$pikkusana" ]] && { echo -n 'pikkusana alkaa kohdalla:'; apu=${isosana%%$pikkusana*}; echo $((${#apu}+1)) ;} || echo 'pikkusanaa ei löydy'
- tästä olisi helppoa tehdä versio joka ilmoittaa kaikki ne paikat joista etsittävä löytyy.
- onko ihme että BASH:ia väheksytään kun tämöisistä super-nopeista ja vakaista on siirrytty epävakaisiin aikasyöppöihin?

tehtävä5: yhdistä kaksi edellistä grep-käskyn kaltaiseksi - siis tarkoituksena on käydä nimettävä tiedosto läpi ja jokaiselta riviltä miltä esittävä löytyy tulostetaan rivinumero ja löytökohta:
Koodia: [Valitse]
function grep2 () { < $2 readarray doku; rivino=0; for n in "${doku[@]}"; do rivino=$(($rivino+1)); apu=${n%%$1*}; [[ "$n" =~ "$1" ]] && { echo -n 'haettava löytyi riviltä: '$rivino' paikalta:'$((${#apu}+1)); echo ;} ; done ;}; grep2 "feature is" /boot/grub/grub.cfg
- siis kutsu on kokonaisuudessaan: grep2 mikä_ilmaisu mistä_tiedostosta. Sanassa mikä_ilmaisu saa olla välilyöntejä mikäli se laitetaan heittomerkkien väliin. Alkuperäinen grep ei edes salli heittomerkkejä ja ilman niitä se toimii hitaasti ja väärin.
- tämä on kolme kertaa hitaampi kuin alkuperäinen grep - mutta paljon nopeampi kuin BASH-skripti tavanomaisilla uusilla käskyillä. Silloin aikoinaan tällaisilla olisi hyvin voinut kilpailla grep-käskyn kanssa. Lisäksi tämän saa muokattua aivan sellaiseksi kuin käyttäjä haluaa.
- näistä nopeista käskyistä muodostetut skriptit lisäksi senjälkeenkun sen toimivat osat on koottu joko  toimivat tai sitten ei - mutta sitä uusien käskyjen raivostuttavaa taipumusta ei ole että alkuunsa saa virheellisiä tuloksia kunnes korjattuaan skiptiä miljoona kertaa alkaa saada oikeita tuloksia yhä useammin. Ovatko uudet käskyt siis paitsi satoja kertoja hitaampia niin myös loogisia pommeja?

73
Keskenkaiken mieleeni tuli että desimaalilaskennan toimiminen kertoo sen että myös suoritusajan mittaus toimisi samoja menetelmiä soveltaen paljon paremmin - joten käsitelläänpä suoritusajan mittausta nyt uudestaan sillä sen käsittely oli aikoinaan kovin epämääräistä.

On totuttu siihen ettei millisekuntia tarkemmin kannata mitään BASH:issa suoritusaikoja ajoittaakaan sillä suoritusajat vaihtelevat aina vähintään joitain millisekunnin kymmenyksiä ja tulkin kutsuminenkin ja sen toimintakin kestää jotain pientä ja epämääräistä - muuten näiden syiden takia BASH:in time-käsky ilmoittaakin erikseen user, sys ja system ajat - ja se aika joka kestää tuloksen selviämiseen kuluu taitaa olla näiden aikojen jonkinsortin summa.

Kaikki lähtee siitä kuinka kauan kestää 'nollasuoritus' elikä käsky: kaksoispiste - joka ei tee mitään mutta tulkkia siinä kyllä kutsutaan. Nollasuorituksen kestoajan luotettava mittaaminen helpottuu oleellisesti kun hyödyntää BASH:in kykyä käsitellä jopa samassa lauseessa käsiteltyä tekstijonoa laskettaessa kokonaislukuna. Vaikka kestoaikaa voi kuvata ainoastaan desimaaliluvulla niin sehän on  kokonaisluku kunhan vaan jättää desimaalipisteen kirjoittamatta - joten laskut onnistuvat ihan hyvin - kunhan vain ottaa huomioon montako niitä desimaaleja pitäisi olla. Mutta tulosten loppukäsittelystä ei tulisi mitään ilman noita vanhoja jo unohdettuja käskyjä joiden erinomaisuutta ei koskaan tajuttu - joten kunnollista ajoitusta ei ole ennen tehty - tai onhan parempi ajoitustapa toki jo aikaisemminkin tunnettu mutta keinoja sen kunnolliseen käyttöön ja oikeellisuuden osoittamiseen ei ole ollut. 
 
Nollasuorituksen kunnollinen ajoitus:
Koodia: [Valitse]
alkuhetki=$(date +%s%N); : ; loppuhetki=$(date +%s%N); aika=$((10#$loppuhetki-10#$alkuhetki)); apu=000000000$aika; apu=${apu: -14: -9}.${apu: -9}; echo ${apu##+(0)} 
- luultavasti käsky: $(date +%s%N) ei ole tarkka. Mutta se tekee suurinpiirtein saman virheen kaksi kertaa: ensimmäisellä kerralla se vaikuttaa plussana ja toisella miinuksena joten ne kumoaa toisensa melkohyvin.
- nollasuoritus on tuo yksinäinen kaksoispiste seuraavan kohdan lopussa: alkuhetki=$(date +%s%N); : 
- nollasuorituksen suoritusaika on oikeastaan aika suuri - paljon suurempi kuin mitä saa kun antaa käskyn: time :  . Miksi time-käsky antaa liian pienen ajan? Vika on nimittäin time-käskyssä sillä tuo tarkka ajoitus on varmasti tarkka - ei varmaankaan nanosekunnilleen mutta yli mikrosekunnilleen. Selitys time käskyn liian pieneen aikaan on se että time-käskyn  real-, user- ja system-ajat ovat pienempiä kuin .0005 joten ne tulkitaan nolliksi - siksi time-käskyn ajoitustulos on lähes millisekunnin liian pieni sillä oikea aika on niiden summa; kyseessä on siis pyöristysvirhe.
- äskeisen selityksen oikeellisuuden varmitus: kirjoita kaksoispisten paikalle: time sleep .001 . Saat kaksi erilaista aikaa. Mutta huomaapa että time-ajoituksen real+user on lähes sama kuin tarkempi ajoitustulos. Joten ei niitä aikaisemmin mainittuja millisekunnin kymmenyksiä oikeastaan ole ollutkaan - lisäksi on unohdettu jo kauan sitten kuinka time-käskyn tulokset tulkitaan.
- kaksoispisteen paikalle voi kirjoittaa mitähyvänsä: yksittäisiä käskyjä, skriptejä tai funktiokutsuja - ja mitä sinne laitetaankin niin sen toiminta-aika määritellään.
- koska tuloksista voi muodostaa keskiarvoa niin esimerkiksi voi vihdoinkin lopullisesti selvittää kumpi käsky on keskimäärin nopeampi yksinkertaisissa tulostuksissa: printf vai echo.

Siitä on ihan liian aikaista puhua mihin kaikkeen muuhun tästä on apua; ehkä vain BASH:in omituisuuksien tutkimiseen.
- saat muuten mielikuvan siitä paljonko nollasuoritus kestää eri suorituskerroilla käyttäen skriptiä:
Koodia: [Valitse]
for n in {1..20}; do alkuhetki=$(date +%s%N); : ; loppuhetki=$(date +%s%N); aika=$((10#$loppuhetki-10#$alkuhetki)); apu=000000000$aika; apu=${apu: -14: -9}.${apu: -9}; echo ${apu##+(0)}; sleep 1; done
-'sleep 1' lopusssa on tarkoitettu rauhoittamaan riehumista - joskus myös BASH kompastelee omiin lahkeisiinsa jos kovin kiirehtii.

74
Potenssiin korotus nopeutui yli kaksinkertaisesti skriptin rakennetta yksinkertaistamalla - nyt potenssiin korotus on yksi funktio. Pienet potenssit lasketaan aina alle 10 millisekunnin.
Eihän tämä vieläkään ole kovin käyttökelpoinen mutta toimii edes jotenkin ja toisaalta sekä nopeus että tarkkuus paranevat kokoajan. Mutta sriptien ominaisuudet paranevat aina kovin hitaasti - sillä jos koettaa kiirehtiä niin homma leviää helposti käsiin - toiminta on sama kuin hiihtomäessä: kun nopeus alkaa kiihtyä kovin nopeaksi niin takapuoli maahan tai jotain muuta hidastetta. Mutta jos jaksaa niin toiminta tuntuu paranevan ikuisesti.

Ja yleensä jossain vaiheessa tulee loikka suorituskyvyssä; tämmöinen nopeuden kaksinkertaistuminen on pientä. Sillä tuntuu olevan niin että jokaisessa asiassa on rajaton määrä ratkaisutapoja - ja meille on opetettu niitä tehottomia ratkaisutapoja. Pitää täysin paikkansa että kokeillessa paljon tulee helposti siirryttyä epäilyttäville alueille - mutta toisaalta jos skripti toimii aina luotettavasti ja vahingoittamatta ympäristöään niin mitä pahaa siinä kokeilemisessa on?

Määränpäänä on koota funktioista kirjasto josta kukahyvänsä voi hakea koneeseensa funktioita ilman että tulisi myrkytetyksi kekseillä, kirjautumatta ja asentamatta mitään ettei kone muuttuisi mitenkään - sillä kaikilla meillä on taipumus temppuhin - eihän siinä mitään mutta kun tuputamme niitä temppuja toisillekin. Hommaa kuvaa mietelause: miksi tehdä helpolla kun voi tehdä vaikeasti?

Hyvän kirjaston yksi ominaisuus on se että se toimii kaikissa tehtävissä samankaltaisin menetelmin eikä kovin usein muuta toimintaansa. Kirjaston kokoaminen onkin todella hidasta - jos keskittyisi yhteen pieneen tehtävään niin vuosi kuluisi kumminkin. Siis kirjaston kokoaminen olisi pitänyt alkaa jo silloin kolmekymmentä vuotta sitten - niin se taisi alkaakin mutta koko kirjasto-idea romutettiin sittemmin.


function potenssi () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko osa-tehtävien jälkeen välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
for n in $(seq $(($2-1))); do
luku1=${1:0:18}; [[ $n = 1 ]] && luku2=$luku1 || luku2=$merkki${tulos##+(0)}; luku2=${luku2:0:18}
$tulosta "annetut numerot: $1 $2"
[[ ${1: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 
(( $summa2 )) && : || summa2=000000000000000000
(( $kokonaisluku )) && tulos=${summa2/*(0)/}$summa1 || { apu=$summa2$summa1; tulos=${apu:0: -$desimaaleja}.${apu: -$desimaaleja} ;}
done
echo; echo tulos laskusta: $1 \^ $2'  . Ylärivi on varmasti oikea arvo bc:stä vertaamista varten ja alarivi tästä skriptistä:'
bc<<<"scale=20; $1^$2"
(( $2 & 1 )) && echo -n $merkki; echo ${tulos##+(0)} ;}

time potenssi -1.23456789012345 10 # tämä on BASH:in merkintätapa laskulle: -1.23456789012345^10. Jos laskun haluaa tehdä jossain skriptissä niin siinä skriptissä merkintatapa on: luku=$(-1.23456789012345 10)

75
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ä.

Koodia: [Valitse]

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ä):

Koodia: [Valitse]
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ä.



76
 Aloin suunnitella kuinka lasketaan kulman sinin arvo Taylorin sarjan mukaisesti. Taylorin sini-sarja on : sin x= x - x³/3! + x⁵/5! - X⁷/7!... ja siinä kulman arvo on radiaaneissa. Sikäli homma oli helppo että kaikki sen tarvitsemat funktiot on tehty jo - funktioiden koodia on paljon mutta itse skriptiä vähän. Ei merkitse mitään että kaikkikin tämän kaltaiset skriptit ovat hitaita ja kovin rajoittuneita - sillä kun ne saa toimimaan on se verraton voitto. Ja BASH:in kanssa on varmaa että mikään ei selviä helposti mutta kaikki selviää lopuksi.

Ja tälläkertaa tein ensiksi kunnon kuvauksen kuinka hommien pitäisi tapahtua enkä vain harakanvarpailla tupakkiaskin kanteen söherrettyä arvoituksellista hieroglyyfiä - eikä siinä vaiheessa mitään ollut tehtynä itse skriptistä.

- se mistä sini pitäisi laskea on x. Siis sen arvoa ei saa koskaan muuttaa eikä käyttää mihinkään muuhun eikä edes nimetä toisin. Ja siitä lasketun sinin merkintä on: sin_x
- laskun nopeus on noin 50ms
- arvot ovat muuten radiaaneissa. Yksi radiaani on noin 57 astetta.
- tarkkuutta rajoittavaksi tekijäksi tulee jakolasku - BASH:issakin on laskuihin pakko käyttää sen 18:sta merkkisille kokonaisluvuille tehtyä laskentamoottoria josta kyllä saa väsättyä nopean desimaalilaskimen mutta se 18:sta merkin rajoitus on jakajalla sittenkin. Lasku on tyyppiä: $((a/b)). Laskuissa tuo b on kertoma ja siinä ei siis saa olla numeroita enemmän kuin 18 - takanollat voi kuitenkin poistaa ja liittää tulokseen laskun jälkeen - niitä takanollia onkin 21-kertomassa neljä.
- mutta toisaalta kertoman laskenta on yksinkertainen tapahtuma eikä vaadi omaa funktiotaan.
- mitä sopisi odottaa tarkkuudeksi tulevan kahden radiaanin kohdalla: viimeinen korjaustermi on: .0000000001865792386 -> tarkkuus on noin kymmenen desimaalia
- korjaustermin merkin määritteleminen: (( $n==19 | $n==15 | $n==11 | $n==7 | $n==3 )) && kerroin=-1 || kerroin=1


***   funktiot kerro, jaa ja yhteenlasku*********************************************************************************************
Koodia: [Valitse]

function kerro18 () {
local apu apu1 apu2
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
luku1=${1:0:17}; luku21=${2:0:17}
# [[ ${#1} -gt 18 || ${#2} -gt 18 ]] && echo laskettavissa liikaa numeroita && return
$tulosta "annetut numerot: "$luku1 $luku2
[[ ${1:0:1} = - || ${2:0:1} = - ]]  && merkki=- || merkki=''''
[[ ${1:0:1} = - && ${2:0:1} = - ]]  && merkki=''
apu1=${1//\-/}; apu2=${2//\-/}
desimaaliosa1=${1##*.};
desimaaliosa2=${2##*.};
[[ ! ${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 jaa () { (( ! $# )) && echo funktion ajokäsky on muotoa: jaa 1 2 . Siitä pitää tulla: .5000000000000000000000000000 && sleep 2 && return
local apu apu1 apu2
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
# kun jakaja kasvaa yli 18 numerosta alkaa tulo epätarkentua kymmenenteen osaansa jokaista ylimääräistä numeroa kohti - jolloin desimaalipisteen jälkeen tulee yksi nolla lisää. Siten vielä 35 numeroinen jakaja antaa jonkinlaisen tuloksen. luku=${luku##+(0); lkm=${#luku}; [[ $lkm>18 ]] && { ekstranollia=printf "%${lkm-18))s" | tr " " 0; luku1=${luku1:0:17} ;}
 
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
$tulosta $kokonaisosa1$desimaaliosa1
$tulosta $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
$tulosta jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}

$tulosta nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.
luku2=${luku2##+(0)}; luku2=${luku2%%+(0)} 
#q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;$tulosta " "$luku1 #"  "${#apu2}"  "$apu

for n in {1..5}; do # muodostetaan tulos-palasia 9 merkkiä kerrallaan
apu=$(($luku1/$luku2)); tulos[$n]=${apu}  # yksi keino palauttaa niitä kadonneita nollia;  (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ; $tulosta " "$luku1"  "${#apu2}"  p"$apu
done

vanhatmerkit=1
for n in {1..5}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]=$nolliatulosteessa${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
#[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
#echo "oikea tulos 54 desimaalilla esitetynä on päällä ja alla tulos tästä skriptistä"
#bc<<<"scale=$((${#tulos}+$((${#nolliatulosteessa})))); $1/$2" | tr -d '\\\n'; echo ' tämä rivi on bc:stä'
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

#---

function yhteenlasku () { # voi käyttää vähennyslaskuunkin
[[ ${1//[^-]/} ]] && m1=-1 || m1=+1; [[ ${2//[^-]/} ]] && m2=-1 || m2=+1
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"000000000000000000"; 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} ;}


# Varsinainen koodi:
x=.5; sin_x=$x;kerroin=$x;apu=$kerroin;  # skriptiin syötetty kulma radiaaneissa
kertoma=1;takanollia='';for n in {3..23..2}; do kertoma=$(($kertoma*$n*($n-1))); (( ${kertoma: -1} )) || { takanollia=$takanollia'0'; kertoma=${kertoma:0: -1} ;}; echo -n $kertoma' '$takanollia
  kerroin=$(kerro18 $x $( kerro18 $x $kerroin) ); echo -n '   '$kerroin;
  apu=$(jaa ${kerroin:0:17} ${kertoma:0:17}$takanollia ); echo -n '   '${tulos:0:25} 
  (( $n==19 | $n==15 | $n==11 | $n==7 | $n==3 )) && sin_x=$(yhteenlasku $sin_x -$apu) || sin_x=$(yhteenlasku $sin_x $apu); echo '  '$sin_x
done

- sitten siirrytäänkin siihen jakolaskun laajennokseen - se nimittäin vaikuttaa tähänkin paljon.
***

Tämä liukuvan-pilkun kertolaskuohjelma kerro9 on hitaampi kuin isoveljensä vaikka tämän tuloksessa saa olla vain 18 merkitsevää numeroa - muuten tuo hitaus johtuu uudesta käskystä nimeltä: seq sillä jo yksi uusi käsky romahduttaa nopeuden.

Tosin tämä on sittenkin BASH-skriptiksi todella nopea. Ja se hyväksyy paljon sellaista josta isoveli menee solmuun - esimerkiksi desimaalipisteeksi kelpaa sekä piste että pilkku - ja tieteelistäkin esitysmuotoa voi sisäänmenevissä luvuissa olla ja niissa voi eksponentin merkkinä olla joko iso tai pieni e. Ja ennenkaikkea skriptin toiminta on helpompi osoittaa oikeelliseksi.

Kun tekee skriptin mihin tehävään tahansa niin alkuunsa skriptin toiminta on yleensä kovin virheelistä. Mutta olipa virhe mikähyvänsä niin se on korjattavissa - ja itseasiassa nuo korjaukset parantavat koodia muutenkin. Mutta nyt on ihan liian myöhäistä yrittää saada tälläisista skripteistä matemaattisessa mielessä moitteettomia joten virtuoosit saivat tässäkin asiassa tahtonsa läpi eli saivat taas tuhottua BASH:ia vähäsen - ja pääsivät ketkut tästäkin tahallisesta teosta kuin koira veräjästä.

Vikojen korjaaminen käy jokaisen virheen jälkeen hitammaksi sillä jokaisen koodimuutoksen jälkeen täytyisi jokainen testi suorittaa uudestaan ettei vaan korjaa siten että joku aikaisemmin tehty lopettaisi oikeintoimimisen. Ja kun korjauksia tulee tarpeeksi monta niin joskus joutuu jopa aloittamaan ihan alusta - tämäkin skripti on aloitettu jo kolmasti uudestaan - kyllä jokainen yritys silti opettaa jotakin - en siis turhaan BASH:ia opetuskieleksi kutsu.

Vikojen korjaaminen nopeasti on liian myöhäistä tehdä tänäpäivänä sillä ne viat löytyisivät vasta yhteisön ponnistuksin ja nykyäänhän BASH-yhteisöt ovat pieniä eikä tämän tasoinen matematiikka kiinnostakaan enää juuri ketään. Mutta silloin aikoinaan BASH-yhteisöt olivat erittäin suuria ja tämmöiset kiinnnostivat monia joten nopeasti näistä olisi silloin oikeellisia tullut.
Koodia: [Valitse]
function kerro9 () { # Tuloksessa voi olla korkeintaan 18 numeroa - välittömästi desimaali-pisteen perässä olevia nollia ei lasketa mukaan eikä myöskään peränolia.
luku1=${1//./,}; luku1=${luku1//E/e}
luku2=${2//./,}; luku2=${luku2//E/e}
luku1=$(printf "%.18e" $luku1)
luku2=$(printf "%.18e" $luku2) #; echo $luku1' '$luku2; read
exp1=${luku1##*e}; exp1=${exp1//+0/+}; exp1=${exp1//-0/-}
exp2=${luku2##*e}; exp2=${exp2//+0/+}; exp2=${exp2//-0/-}
# exp3=$(($exp1+$exp2+1))

luku1=${luku1%%e*}; luku1=${luku1%%+(0)}
luku2=${luku2%%e*}; luku2=${luku2%%+(0)}

[[ ${luku1:0:1} = '-' ]] && { merkki1=-1; luku1=${luku1:1} ;} || merkki1=1
[[ ${luku2:0:1} = '-' ]] && { merkki2=-1; luku2=${luku2:1} ;} || merkki2=1 

kokonaisosa1=${luku1%%[,.]*}
kokonaisosa2=${luku2%%[,.]*}
kokonaistentulo=$(($kokonaisosa1*$kokonaisosa2))
kokonaisia=${#kokonaistentulo}
exp3=$(($kokonaisia+$exp1+$exp2)) #; echo $exp3; read

# echo; bc<<<"$1*$2" # bc kertoo useasti lopputuloksen joka on ihan varmasti oikea ja siihen on kiva verrata. Mutta tässä on paljon sellaistakin josta bc kirjoittaa vain härskin viestin.
tulos=$((${luku1//,/}*${luku2//,/}))'00000000000000000000000000000000000000'; [[ ${kokonaistentulo:0:1} -eq 9 ]] && [[ ${tulos:0:1} -eq 1 ]] && exp3=$(($exp3+1))
[[ $(($merkki1*$merkki2)) -eq -1 ]] && echo -n '-'
[[ $exp3 -gt -1 ]] && apu2=${tulos:0:$exp3}.${tulos:$exp3} ||  apu2=.$(printf '0%.s' $(seq ${exp3:1}))${tulos%%+(0)}
[[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}


# kohtia jotka kannattaa tarkistaa:
kerro9 1 1
kerro9 -1 1
kerro9 -1 -1
kerro9 1 -1
kerro9 0 0 
kerro9 1.1 1,1                                       # on parempi että laskutoimitus onnistuu vaikka se ei teoriassa ihan oikein olekaan.
kerro9 4 4
kerro9 1000001.2345 1E-5

kerro9 1234567891 1234567892
kerro9 123456789012345678 2.2
kerro9 123.4567891 1234567.89
kerro9 1234567891 1234567892E-118                  # kerrottavien esitysmuoto saa olla tieteellinenkin
kerro9 .0000000000001234567892 .000000001234567891 # välittömästi desimaalipisteen perässä olevia nollia ei lasketa merkkilukuun.
kerro9 12345678000000000000 123456780000000000000  # peränollatkaan eivät ole merkitseviä nomeroita
 
kerro9 3.16227766 3.16227766                       # dekadin vaihtuminen ulostulosssa on syytä tarkistaa, siis se että kun
kerro9 3.16227767 3.16227767                       # ulostulo siirtyy uuteen dekadiin muuttuuko desimaaalipisteen paikka oikealla hetkellä
kerro9 10 100
 
kerro9 1000000001 .999999999
kerro9 +1234567891 1234567892                       # + merkkiä voi käytää osoittamaan luvun positiivisuutta eikä sitä silloin käsitetä laskutoimitukseksi.
kerro9 123.4567891 1234567.89

***

Ei kukaan viitsi tehdä noilla vanhoilla käskyillä skriptiä jos tietää että kun tekee koodia päivän joutuu kommentoimaan sitä viikkoja - sitäpaitsi kommenttejakin pitäisi selventää omilla kommenteillaan - lisäksi minulla on sellainen käsitys että jos kommentointi on moitteeton, vuokaaviot on tehty ja vertaisarvioita hankittu niin kukaan ei edes lue niitä - saatika ymmärrä kommentteja oikein jos skriptiä täytyy korjata. Melko turhaa touhua koko kommentointi. Tämä johtaa tosin siihen että myöhemmin ei korjata vaan tehdään uusi - mutta homma pelaa edes jotenkin.

Myös printf on hidas uusi käsky. Näillä uusilla massivisilla käsky-järkäleillä on sekin paha piirre että niillä on ne ominaisuudet mitkä niillä on ja jos käsky ei miellytä niin asialle ei voi tehdä mitään. Mutta jos uusia käskyjä jäljitellään vanhojen nopeiden käskyjen joukolla niin ominaisuudet voi muodostaa aivan millaiseksi haluaa - esimerkiksi voi tehdä käskyn: printf2 jolla on printf:n ne ominaisuudet jotka ovat tarpeen senhetkisessä hommassa - ja kenties hieman uusiakin ominaisuuksia - ja lopputulos on silti nopea. Mutta vasta monien versioiden jälkeen toiminta on juuri haluttu.

Tässä on tarkoitus ainoastaan matkia sitä kuinka printf kykenee tekemään sisäänmenevät luvut 'saman-näköisiksi' sillä esimerkiksi äsköinen skripti ei enempää kaipaa.  Ensimmäinen versio voisi olla vaikka:
Koodia: [Valitse]
function fprint2 () { luku1=${1##+(0)}; [[ -z "${luku1##*e*}" ]] && { exp1=$((${luku1##*[eE]})); luku1=${luku1//e*/} ;} || exp1=0; [[ ${luku1:0:1} = . ]] && { apu=${luku1%%[1-9]*}; exp1=$(($exp1-${#apu})); luku1=${luku1##*[.0]} ;} || exp1=0 ; luku1=${luku1//./}; echo ${luku1:0:1}.${luku1:1}e$exp1 ;}

# kokeet (luvullahan ei enää ole etumerkkiä):
luku1=1234.56e8; fprint2 $luku1
luku1=1234.56; fprint2 $luku1
luku1=.123456e-8; fprint2 $luku1
luku1=0.123456e-8; fprint2 $luku1
luku1=.0000123456e-8; fprint2 $luku1 # koska printf hyväksyy tämmöisiä kummallisia lukuja niin printf2:n on syytä hyväksyä ihan samanlaisia kummajaisia.
luku1=1; fprint2 $luku1
luku1=11; fprint2 $luku1

***
- nyt meni tuon printf:n kanssa miettimiseksi ja välillä kannattanee tehdä jotakin muuta.

- ei näillä laskuilla ole toistaiseksi muuta tehtävää kuin osoittaa ettei pitäisi sanoa: BASH ei osaa .... -  ja onhan nämä hyvää dementian torjumista.

- jos matemaatikkoja ei päästetä esittämään derivaattoja ja limeksiä niin e:n laskeminen Taylorin sarjakehitelmällä on todella yksinkertaista: e=1+1+1/2!+1/3!+1/4! .....

- sen kykenee laskemaan aivan tavanomaisin keinoinkin, mutta silloin tuloksessa  on kovin vähän numeroita ja laskenta on hidasta:
Koodia: [Valitse]
kerroin() { (( $1 )) && echo $(( $1 * $(kerroin $(($1 - 1))) + 1 )) ;}
summa() { apu=$(echo $(( 10**($1-1) * $(kerroin $1) / $(($(seq -s* $1))) ))) ;}
time for i in {1..11}; do summa $i; done; echo 'e='2.${apu:1}

Mutta desimaalilaskentaa käyttäen e:hen saa paljon enemmän numeroita ja nopeamminkin:


77
BASH:in palauttaminen järkevään kuntoon täytynee alkaa inhottavimmasta päästä: BASH:in matematiikasta. Tai eihän BASH:illa matematiikkaa oikeastaan ole vaan ainoastaan kyky kokonaisluku laskentaan - kyky jota ei koskaan käytetä sillä kyky kyetä käsittelemään myös desimaalilukuja on lähes ehdoton. Desimaalilukujen käsittelemiseksi täytyy siis aluksi tehdä funktiot. Selvitetään ensin laskennan merkintätapaa:

Kaikissa kielissä matemaattiset toiminnot suoritetaan samallatavoin, mutta laskujen suorittamisen merkintätavat vaihtelevat paljon - ja kunkin merkintätavan hyvät ja huonot puolet paljastuvat vasta kun niitä käyttää paljon - mutta useimmat eivät koskaan edes kokeile muita kuin normaalia merkintätapaa.  Mutta muutama laskennan merkintätapa: 

                       Normaali merkintätapa: a=1; b=2; c=a+b
Käänteinen puolalainen merkintätapa: a=1; b=2; c=ab+ 
                        BASH:in merkintätapa: a=1; b=2; c=$(($a+$b)) . Tietokoneohjelmassa merkintätavan monimutkaisuus menettää merkityksensä - vaikka eihän se sentään merkityksetöntä ole.
               
- merkki + kutsuu niissä kaikissa funktiota joka selvittää kuinka homma hoidetaan.
- muissa kielissä laskun jäsenet voivat olla desimaalilukujakin - mutta ei BASH:issa. Joten kun muissa kielissä on desimaalien käsittelyyn jo tehtynä funktioita - käytät niitä vaikka et edes tietäisi että sellaisia tarvitaan - funktiot ovat nykyään usein matematiikkaprosessorien langoituksessa - mutta BASH:iin nuo funktiot täytyy tehdä itse. Ja käytettässsä funktiota laskuihin muuttuu toiminnon merkintätapakin: a=1.1; b=2.2; c=$( ynnää $a $b ) .
- mutta ei sen funktion nimeltä: + hommaa pysty kukaan toinen tekemään joten funktiota: + täytyy kutsua funktiossa: ynnää.

***             
             
Desimaali-laskun suorittamiseksi täytyy ensin laskea missä desimaalipiste tulee tuloksessa olemaan. Tämänjälkeen poistetaan luvuista desimaalipisteet ja suoritetaan laskutoimitus saaduilla kokonaisluvuilla. Sitten vain kirjoitetaan tulos sijoittaen desimaalipiste lasketulle paikalle. Tietokoneiden alkuaikoina oli vain  kokonaislukulaskimia ja haluttessa saada desimaalinen tulos toimittiin kuten edellä kuvattiin.

Kyllä virtuoosit siis ilmanmuuta tämän tiesivät mutta heillä oli jo toinen skriptikieli kiikarissa ja he halusivat tästä uuden skriptaamisen kuninkaaan - sillä BASH oli jo niin mennyttä ja vaikeata. Voidakseen osoittaa BASH:in olevan surkimus desimaalilaskujakin oli estettävä kehittymästä. Helppo nakki - tehtiin uusi käskykanta joka oli muissa tehtävissä parempi kuin entinen mutta desimaalilaskuissa se oli kammottavan hidas. Vanhoilla käskyillä desimaalilaskenta onkin tuhansia? kertoja nopeampaa kuin uusilla käskyillä - siis jos vanhoilla käskyillä sai aikaan millisekunnissa toimivan niin uusilla se kestäisi sekunteja. Tämä johtaa siihen että uusia käskyjä käytettäessä testauksesta tulee niin hidasta että luullaan ettei homma edes toimi.

Noiden vanhojen käskyjen kirjoitusasu on kummallinen joten ne ovat vaikeita kirjoittaa ja muistaakin. Mutta ne ovat nopeita ja ennenkaikkea kääntäjä-ystävällisiä -> kääntämisessä on käytössä  sama cache-menettely kuin kaikessa muussakin. Ja cachen koolla on rajansa ja kun sinne käänetään käskyjä niin noita vanhoja ja pieniä mahtuu sinne tusinoittain mutta uusia ja suuria käskyjä vain muutama. Joten nopeuseroa käskyillä on käsittämättömän paljon sillä uusilla käskyillä tehdyssä skriptissä kutsutaan kääntäjää vähänväliä mutta yksinomaan vanhoista käskyistä tehdyssä vain kerran.

Mutta eivät vaikeudet kirjastokelpoisen funktion valmistumiseen lopu sillä kirjastoahan ei ole. Eikä yksi tyyppi niitä voi tehdä - se olisi ihan liian hidasta ja yksipuolista. Mutta kannattanee yrittää.

***

Akutilanne: haluat laskea: yhteen kaksi korkeintaan 72 numeroista positiivista lukua(merkin huomiooon ottaminen monimutkaistaisi esimerkin vaikeaksi käsittää), desimaalipiste saa kummassakin sijaita missävaan. Toiminta on silloin:
     
       Otetaan esimerkki joka kirjoittaan ( siis desimaalipisteet samalle kohtaa alekkain ja desimaaliosissa pienemmän numerot täytetään nollilla:
       12345678901234567890.2000000000000000000000000000
                        +73.4567890123456789012345678901
     
Sitten vaan laskemaan - desimaalipisteen paikka pistetään muistiin esimerkiksi sen perusteella kummassa on kokonaisoasassa enemmän numeroita - desimaalipiste poistetaan ja jaetaan loppu18 merkin palasiin lopusta alkaen ja lasketaan palaset yhteen alkaen vähiten merkityksellisestä päästä ja mikäli jonkun palasen summaan tuleekin 19 numeroa niin se viimeinen numero lisätään seuraavaan palaseen poistaen se alkuperäisestä palasesta. Sitten vain laitetaan palaset peräkkäin ja kirjoittetaan oikealla hetkellä desimaalipistekin.
Koodia: [Valitse]

function summaa72 () {
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia ja vertailutulos bc:stä. Vaihtoehdot:tulosta=echo ja tulosta=:
$tulosta

[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desi1=${luku1##*.}
desi2=${luku2##*.}
koko1=${luku1%%.*}
koko2=${luku2%%.*}

# desimaaliosien tulee olla yhtäpitkät. Lyhyemmän perään kirjoitetaan nollia silä ne eivät muuta arvoa.
(( ${#desi1} >= ${#desi2} )) && desipituus=${#desi1} || desipituus=${#desi2}
desi1=$desi1'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi2=$desi2'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi1=${desi1:0:$desipituus}; $tulosta desi1:$desi1
desi2=${desi2:0:$desipituus}; $tulosta desi2:$desi2 ;$tulosta

summattava1='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko1$desi1; summattava1=${summattava1: -72}; $tulosta $summattava1
summattava2='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko2$desi2; summattava2=${summattava2: -72}; $tulosta $summattava2 ; $tulosta

luku11=${summattava1:0:18}; luku12=${summattava1:18:18}; luku13=${summattava1:36:18}; luku14=${summattava1:54:18}; $tulosta $luku1{1..4}"  "
luku21=${summattava2:0:18}; luku22=${summattava2:18:18}; luku23=${summattava2:36:18}; luku24=${summattava2:54:18}; $tulosta $luku2{1..4}"  "; $tulosta

apu14=$((10#$luku14+10#$luku24)) ;          [[ ${#apu14} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto1; apu14=${apu14:1:18} ;} || ylivuoto=0
apu13=$((10#$luku13+10#$luku23+$ylivuoto)); [[ ${#apu13} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto2; apu13=${apu13:1:18} ;} || ylivuoto=0
apu12=$((10#$luku12+10#$luku22+$ylivuoto)); [[ ${#apu12} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto3; apu12=${apu12:1:18} ;} || ylivuoto=0
apu11=$((10#$luku11+10#$luku21+ylivuoto))
apu=$apu11$apu12$apu13$apu14

$tulosta "ylemmällä rivillä varmasti oikea tulos bc:stä ja toisella rivillä mitä tämä skripti antaa:"
bc<<<"$1+$2" | tr -d ' \\\n'; echo
# poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipistekin jos se jää viimeiseksi
apu2=${apu:0: -$desipituus}.${apu: -$desipituus}; apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}
   
time summaa72 55555555555555555555555555555.55555555555555555555555555555555555555555 6666666666666666666666666666666.6666666666666666666666666666666666666666

- ei tämä kyllä vielä ihan moitteeton ole mutta kelpaaa jo käyttöönkin.
- funktion nopeus on 1ms jos poistaa rivin: $tulosta $(bc<<<"$1+$2") - sillä tuota riviä tarvitaan vain antamaan varmasi oikea tulos vertaamista varten sillä onhan bc todettu varmasti oikeelliseksi ja se on varmasti myös tarpeeksi tarkka. Mutta toiminnataan tämä funktio on paljon nopeampi kuin bc.

***

Olisihan näihin BASH:in matemattisiin ongelmiin ihan yleisestikin käytettyjä menetelmiä niinkuin bc ja AWK. Mutta ihan mielenkiinnosta koetan mihin BASH itse pystyy - ja sitäpaitsi pienissä perus-laskutoimituksissa BASH on jo paras. Ja mikäli BASH:in funktioista tehtäisiin kirjastofunktiot niin ne olisivat myös yksinkertaisimmat

BASH:in kehittäjät olivat aikoinaan toteuttaneet toimivat menetelmät moitteettomiin peruslaskuihin ja virtuoosit ovat suunnanneet nuo menetelmät lattian alle. Nimittäin kaikki haluavat päästä BASH:ista eroon sillä BASH laajeni aikoinaan niinpaljon etteivät edes virtuoosit sitä enää täysin hallitse - ei semmoista voi korjata vaan ainoastaan pyrkiä siitä eroon. Ja mikäli osoitettaisiin että BASH hallitsee myös desimaalit olisi siitä paljon hankalampaa päästä eroon.
- siis tosiaan kymmenistätuhansista asioista on tehty paksuja raamattuja täynnä käskyjä ja menetelmien selityksiä. Kenelläkään ei ole pienintäkään mahdollisuutta edes lukea kaikkia niitä - ja nykyäänhän niistä on moni historian hämärissä.

Alkuunsa kielen matemaattisia kykyjä hiotaan suorittamalla "näyttäviä laskuja". Muut kielet harrastivat näitä "näyttäviä laskuja" kolmekymmentävuotta sitten - sillä ne on pakko läpikäydä joskus. Vaikka BASH on kyllä taitanut ne jo silloin kauan sitten niin kukaan ei ole esittänyt toimivaa koodia - joten niitä täytyy esittää nyt:

Tarvitsin taulukkoa jonka ensimmäinen jäsen on .5 ja seuraavat jäsenet aina puolta pienempiä kuin edellinen. Toteutus BASH:illa onnistui hyvin - lukujen tarkkuus oli 26 numeroa ja siihen tarvittiin 86 kahdella jakamistaja. Koko 86:n laskun sarja kesti noin 10 ms - BASH:ilta se on nopeaa vaikka muilla kielillä se onkin hidasta. Skripti on näennäisesti iso, mutta muissa kielissä vastaavat ohjelmat ovat paljon isompia, mutta niissä toiminta tapahtuu kirjastoissa, siis katseelta piilossa. Skriptistä tuli tällainen:
Koodia: [Valitse]

time { number=.1; for n in {1..86}; do apu=${number%%[1-9]*}; apu2=${number:${#apu}}'00000000000000000';apu2=${apu2:0:18}; number=$(($apu2/2))'00000000000000'; (( ${apu2:0:1} == 1 )) && number=0$number; number=${number%%+(0)}; number=$apu$number; echo $n; bc<<<"scale=26;1/(2^$n)" | tr -d '\\\n'; echo; echo .${number:2:26}; echo; done ;}
- bc tarjoaa taas vain vertailuarvon. Mikäli nopeutta haluaa niin se on poistettava samoin kun turhat echot
- tämä on kokoajan melkoisessa kehitysvaiheessa

***

Törkeä temppu on väittää että BASH on helppo kieli mutta ettei se mihinkään kykenekään ja sen kykenemättömyydenkin se tekee hitaasti. Tosiasiassa BASH on niin monipuolinen ja monimutkainen että ohjelmointi sillä on niin hidasta ja haastavaa ettei sitä kannata tehdä jos haluaa työstään tuloksia järkevässä ajassa - sillä hyvistä käskyistä ja menetelmistä ei missään puhuta - ja rakenteeltaan ne ovat sellaisia ettei niitä löydä googlaamallakaan muuten kuin sattumalta. Sensijaan opetuskielenä BASH on loistava koska sillä edistyneemmät menetelmät täytyyy koota itse käyttäen käskyjä joita ei helposti löydä - mutta sellainen vika BASH:illa on että jos oppilaallasi on uskomaton tuuri löytää hän paremmat käskyt ja hänen tekemänsä toiminnot jättävät sinut toiseksi. Nopea BASH ei ole milloinkaan mutta ei kovin hidaskaan jos käyttää oikeita käskyjä.   

Kesti kymmenen vuotta käsittää että ulkomaisilla verkkosivuilla lähes poikkeuksetta käy seuraavalla tavalla: joku esittää kysymyksen. Kun siihen on esitetty kymmenkunta huonohkoa vastausta niin joku ohjelmoija ei enää kestä vaan esittää kertaluokkia paremman vastauksen. Mutta ei tämä hyvä vastaus mitään muuta vaan niitä kehnoja vastauksia tulee edelleen - ja jopa entistä enemmän. Suomesta ei ole niin paljoa dataa että voisi sanoa mitään.

***

Kuinka kauan BASH:issa kestää yhteenlasku? Esimerkiksi seuraavassa miljoona yhteenlaskua kestää noin 400ms, siis .4 mikrosekuntia per yheenlasku. Ei tämä tosin kuvaa yhden yhteenlaskun noin 50 mikrosekunnin aikaa kovinkaan hyvin mutta kertoo sentään mitä on mahdollista BASH:illa ääri-tapauksessa saavuttaa. Tulos on muuten ihan oikein joten kyllä se laskee - ja tosiaan yhteenlaskuna eikä aritmeettisen sarjan kaavalla.
Koodia: [Valitse]
mat=({1..1000000}); apu=$(printf %s+  ${mat[@]}); time echo $(($(echo ${apu%+})))

***

Kyllä jotkin BASH:in normalitkin käskyt ymmärtävät desimaalien olemassaolon. Esimerkiksi annapa käsky:
Koodia: [Valitse]
seq .111111111111111111 .111111111111111111 11

 

78
***

Nämä skriptini ovat yritystä löytää BASHin perusta - skriptit ovat mielettömiä mutta kukaan ei voi tietää millaisiin tuloksiin ne johtavat. Ja muuten: kaikki nopeat käskyt briljanteraa tekstinkäsittelyssä vaikka on niistä apua laskennassakin.

BASH:issa on kammottava määrä käskyjä joista ei koskaan puhuta. Ne ovat nopeita ja tekevät pieniä ihmeitä. Esimerkiksi käskyt seuraavan skriptin tulostukseen: poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipiste silloin kun se jää viimeiseksi:
Koodia: [Valitse]
apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.}
Toiminto on salaman-nopea ja se on ollut olemassa aina mutta jokainen joka noita toimintoja nykyisin tarvitsee tekee varmasti toimintoa varten pitkän ja hitaan skriptin - ja vain koska 'advance guidet' pitävät huolta siitä että noiden kaltaiset käskyt pysyvät unholassa. Ei niistä tosin paljoa BASH-raamatuissakaan puhuta. Mutta netti ei koskaan unohda ja sinne kerkisi kauan sitten tulla monia kirjoituksia niistä - tosin noiden käskyjen nopeudesta ei ole hyötyä jos niiden ohella käyttää uusi käskyjä. Eikä missään ei ole kattavaa yhteenvetoa.

Tehdessäni skiptiajuria kohtasin toisenlaisia ihmeellisiä käskyjä sllä se on pääasiassa tekstikäsittelyä - taas aivan toisenlainen maailma. Montakohan maailmaa BASHissa on?

***

Koska nyt  haukun virtuooseja pystyyn niin halusin varmistaa että haukun toimivien asioiden perusteella - tälläkinkertaa on kyse desimaalimatematiikasta - mutta tätä ennenhän on ollut kaikenlaista muuta - paljon merkityksellisempääkin. Jokatapauksessa tein taas yhden mielettömän skriptin jossa on runsaasti moninumeroista laskentaa - mutta toisenlaista laskentaa kuin aikaisemmissa skripteissä joten uudetkin laskut tulee testattua. BASH itse laskee muuten montakertaa nopeammin kuin matematiikka-ohjelmat joten niitä ei laskennassa käytetä - varsinaisessa matematiikassa BASH jää kyllä paljon huonommaksi ja sitähän tässä koetetaan nopeuttaa - toistaiseksi huonosti menestyen. Mutta kyse ei tälläkertaa ole varsinaisesti matematiikasta vaan siitä että soiminto ylipäätään onnistuu jollaintavalla - se että se toimii näinkin hyvin on kylläkin mukavaa.

Skripti muuntaa digitaaliluvun desimaaliosan kymmenjärjestelmään. Esimerkiksi luvun .11100000001000000010000000010000000010000000100000001001 muuntaminen kymmenjärjestelmään:
skripti:                          0.875490192331227615183197488403320317865025843484497
netin muunnosmoottori: 0.87549019233122761519

- onko kyseessä pyöristysvirhe vai laskeeko muunnosmoottori liian vähillä numeroilla? Sillä 'tarkkuuskato' näissä laskuissa on niin suuri että saa pelätä ettei tuo skriptinkään numeromäärä vielä riitä vaan kaikki nuo ylimääräiset numerot ovat jo potaskaa - ne ovat tuossa vain siksi että näkisitte paljonko niitä on.
Koodia: [Valitse]

function summaa72 () { # viimeisin versio
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia ja haetaanko vertailutulos bc:stä. Vaihtoehdot:tulosta=echo ja tulosta=: . Vaatii siis yksinkertaisen koodimuutoksen.
$tulosta
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1".0"
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2".0"
desi1=${luku1##*.}
desi2=${luku2##*.}
koko1=${luku1%%.*}
koko2=${luku2%%.*}

# desimaaliosien tulee olla yhtäpitkät. Lyhyemmän perään kirjoitetaan nollia.
(( ${#desi1} >= ${#desi2} )) && desipituus=${#desi1} || desipituus=${#desi2}
desi1=$desi1'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi2=$desi2'0000000000000000000000000000000000000000000000000000000000000000000000000000'
desi1=${desi1:0:$desipituus}; $tulosta desi1:$desi1
desi2=${desi2:0:$desipituus}; $tulosta desi2:$desi2 ;$tulosta

summattava1='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko1$desi1; summattava1=${summattava1: -72}; $tulosta $summattava1
summattava2='0000000000000000000000000000000000000000000000000000000000000000000000000000'$koko2$desi2; summattava2=${summattava2: -72}; $tulosta $summattava2 ; $tulosta

luku11=${summattava1:0:18}; luku12=${summattava1:18:18}; luku13=${summattava1:36:18}; luku14=${summattava1:54:18}; $tulosta $luku1{1..4}"  "
luku21=${summattava2:0:18}; luku22=${summattava2:18:18}; luku23=${summattava2:36:18}; luku24=${summattava2:54:18}; $tulosta $luku2{1..4}"  "; $tulosta

apu14=$((10#$luku14+10#$luku24)) ;          [[ ${#apu14} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto1; apu14=${apu14:1:18} ;} || ylivuoto=0
apu13=$((10#$luku13+10#$luku23+$ylivuoto)); [[ ${#apu13} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto2; apu13=${apu13:1:18} ;} || ylivuoto=0
apu12=$((10#$luku12+10#$luku22+$ylivuoto)); [[ ${#apu12} -gt 18 ]] && { ylivuoto=1; $tulosta ylivuoto3; apu12=${apu12:1:18} ;} || ylivuoto=0
apu11=$((10#$luku11+10#$luku21+ylivuoto))
apu=$apu11$apu12$apu13$apu14

$tulosta "ylemmällä rivillä varmasti oikea tulos bc:stä ja toisella rivillä mitä tämä skripti antaa:"
$tulosta $(bc<<<"$1+$2")
# poistetaaan etunollat kokonaisosasta, takanollat desimaaliosasta ja desmaalipiste jos se jää viimeiseksi
apu2=${apu:0: -$desipituus}.${apu: -$desipituus}; apu2=${apu2##+(0)}; [[ ${apu2//[^.]/} ]] && apu2=${apu2%%+(0)}; echo ${apu2%.} ;}

function jaa () { (( ! $# )) && echo funktion ajokäsky on muotoa: jaa 1 2 . Siitä pitää tulla: .5000000000000000000000000000 && sleep 2 && return
tulosta=: # yhdessä paikassa päätetään tulostetaanko välituloksia. Vaihtoehdot:tulosta=echo ja tulosta=:
 
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
$tulosta $kokonaisosa1$desimaaliosa1
$tulosta $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
$tulosta jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}; $tulosta nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; [[ ${luku1//[^.]/} ]] && luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.!!!
luku2=${luku2##+(0)}; [[ ${luku2//[^.]/} ]] && luku2=${luku2%%+(0)} 
q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;$tulosta " "$luku1 #"  "${#apu2}"  "$apu

for n in {1..6}; do # muodostetaan tulos-palasia 
apu=$(($luku1/$luku2)); tulos[$n]=${apu}; (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' # on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ; $tulosta " "$luku1"  "${#apu2}"  p"$apu
done

vanhatmerkit=1
for n in {1..6}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]=$nolliatulosteessa${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
#[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

function muunna () { local apu7; apu7=$1;luku=0; for n in $(seq $((${#apu7}+1))); do  (( ${apu7:$n-1:1} )) && luku=$(summaa72 $luku ${taulu[$n+1]});done ; echo $luku ;} 

unset taulu;taulu[1]=1;apu=1;for n in {2..57}; do apu=$((2*$apu)); apu8=$(jaa 1 $apu); taulu[$n]=${apu8%%+(0)};  echo $n"  "${taulu[$n]}; done;   # taulukon muodostaminen

muunna 111 # esimerkkikutsu
 

***

Tässä ei varsinaisesti ole kyse desimaalimatematiikasta vaan kaikenmaailman guidejen saattamisesta naurunalaiseksi - ne apinoivat kaikki samoja rajoittuneita alkeita.. Kyllä ne silti on syytä oppia mutta niissä ei tosiaankaan ole mitään edistynyttä - ja valitettavasti tilanne on tällähetkellä se ettei mitään parempaakan ole. BASH raamatutkin ovat kovin yksipuolisia ja niistä on vaikea oppia mitään. BASH-maailma on ainakin kymmeniä kertoja suurempi kuin mitä ne antavat uskoa.

Ja desimaalimatematiikka ei ainoastaan toimi vaan on lisäksi salaman-nopeaa - alle 1ms.

Voitte olla varmoja että heitä kirpaisee jos tämä joskus heidä kurkustaan alas tungetaan - sillä homma on karmea: tappoivat oman lapsensa. Sillä tuo nopeutus ei koske yksinomaan  matematiikkaa vaan myös tekstinkäsittelystä tulee samoilla käskyillä erittäin nopeaa.

Ja vaikka ilmanmuuta koettavat vaieta desimaalilaskennan kuoliaaksi niin minkäs sille voi että tämä toimii. Syy skriptin esittämiseen onkin se ettei tällaisia voi puhua esittämättä kuinka homma hoidetaan.

Aikoinaan  BASH oli parhaimmillaan koeteltaessa toimiiko joku teoria käytännössä - syy BASH-skriptin tekoon oli selvittää kannattaako tehdä kunnollinen ohjelma jollain kunnon kielellä - nimittäin BASH:illa saa nopeasti tehtyä välttävästi toimivan skriptin. Mutta siinä hommassa säällisen nopeat peruslaskutoimitukset desimaali-luvuilekin ovat melkein välttämättömiä. Desimaalilaskuihin käytettiinkin matematiikkaohjelmaa, esimerkiksi bc:tä sillä luultiin ettei BASH desimaalilaskuihin kykene. Mutta kyllä kykenee ja pienehköissä peruslaskutoimituksissa BASH on jopa paljon nopeampi.

Desimaalilaskut suoritetiin silloin aikoinaan kokonaisluvuilla isoissakin koneissa - joten kyllä senaikaiset virtuoosit tiesivät että myös BASH kykenee niihin. Mutta he välttelivät puuttumasta koko asiaan sillä se on sellainen salahautojen viidakko että paraskin virtuoosi haksahtaisi joskus ja sitä he eivät kestä - joutua naurettavuuden kaakinpuuhun. Niinpä he eivät puhuneet koko asiasta - mikä antaa sellaisen mielikuvan etteivät desimaalilaskut BASH:issa edes onnistu. Ja oli heillä laho selityskin: BASH:in matematiikkamoottori ei osaa desimaalilaskuja eikä BASH:in muuttujien tyyppimäärityksissäkään desimaaliluvuista mitään puhuta. Mutta tuo tyyppimääritys on tahallinen väärinymmärrys sillä tekstijonojahan ne desimaaliluvut vain ovat.
 
Mutta siis niitä salahautoja on: jakolaskua vaivaa esimerkiksi 'katoavien nollien syndrooma'. Ja kun nolla katoaa on senjälkeinen tulos pelkkää potaskaa. Tämän korjaaminen on henkimaailmasta: jakolasku ei esitä etunollia -> jos jakolaskun tulos on 010 (esimerkiksi 100/10) niin näytetään vain 10. Mutta vaikka tähän löytyikin korjaus niin jakolaskua täytyy vielä kauan testailla ja kenties krjailla.

Mutta kyse ei ole mitättömän piennistä jakolaskuista vaan esimerkiksi: jaa .123456 1233457890123.23  josta tulos on: .00000000000010008935123651930204696 - joka on kyllä ihan oikein - ei niitä kadonneita nollia silloin löydä kun etsii niitä.

- itse skripti vie aikaa alle millisekunnin. bc tässä sitä aikaa vie laskiessaaan varmasti oikeaa arvoa johon verrata tämän skriptin tulosta. Kommentoi bc pois niin näet.

Koodia: [Valitse]
function jaa () { (( ! $# )) && echo funktion ajokäsky on muotoa: jaa 1 2 . Siitä pitää tulla: .5000000000000000000000000000 && sleep 2 && return
 
[[ ${1//[^.]/} ]] && luku1=$1 || luku1=$1"."
[[ ${2//[^.]/} ]] && luku2=$2 || luku2=$2"."

desimaaliosa1=${luku1##*.}
desimaaliosa2=${luku2##*.}
kokonaisosa1=${luku1%%.*}
kokonaisosa2=${luku2%%.*}

#desimaaliosat yhtäpitkiksi:
(( ${#desimaaliosa2} >= ${#desimaaliosa1} )) &&
{ apu=$desimaaliosa1"00000000000000000000"; desimaaliosa1=${apu:0:${#desimaaliosa2}} ;} || { apu=$desimaaliosa2"00000000000000000000"; desimaaliosa2=${apu:0:${#desimaaliosa1}} ;}
#echo $kokonaisosa1$desimaaliosa1
#echo $kokonaisosa2$desimaaliosa2

#kokonaisosat yhtäpitkiksi:
(( ${#kokonaisosa2} >= ${#kokonaisosa1} )) &&
{ apu="00000000000000000000"$kokonaisosa1; kokonaisosa1=${apu: -${#kokonaisosa2}} ;} || { apu="00000000000000000000"$kokonaisosa2; kokonaisosa2=${apu: -${#kokonaisosa1}} ;}
luku1=$kokonaisosa1$desimaaliosa1;luku1=$((10#$luku1))
luku2=$kokonaisosa2$desimaaliosa2;luku2=$((10#$luku2))
echo jaettava:$luku1"   "jakaja:$luku2

(($luku1 >= $luku2)) && { apu=$(($luku1/$luku2)); kokonaisiatulosteessa=${#apu}; nolliatulosteessa='' ;} || { apu=$(($luku2/$luku1)); apu=${apu//[1-9]/0}; nolliatulosteessa=${apu:1}; kokonaisiatulosteessa=0;}

echo nolliatulosteessa:$nolliatulosteessa"   "kokonaisiatulosteessa:$kokonaisiatulosteessa   

# tähänasti on selvitetty desimaalipisteen paikkaa. Nyt aletaan laskea numeroita.
unset tulos # vain varmistus että kaikki on tuloksessa tämänjälkeen uutta
luku1=${luku1##+(0)}; luku1=${luku1%%+(0)} # poistetaan etu-ja takanollat sillä tästälähtien ne vain haittaavat.
luku2=${luku2##+(0)}; luku2=${luku2%%+(0)} 
# seraava oli: q=$(($luku1%$luku2));q=$((10**$((${#q}-1)))) # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
q=$(($luku1%$luku2));((q>1)) && q=$((10**$((${#q}-2)) )) || q=1 # siis q on jakojäännös esitettynä siten että ensimmäinen numero on 1 ja lopuista numeroista tulee nollia - viimeinen nolla poistettuna
luku1=$luku1'0000000000000000000'; luku1=${luku1:0:18} ;echo " "$luku1 #"  "${#apu2}"  "$apu
for n in {1..6}; do # muodostetaan tulos-palasia 9 merkkiä kerrallaan
apu=$(($luku1/$luku2)); (( $(($luku1%$luku2)) < $q )) && apu=$apu'0'; tulos[$n]=${apu}  # yksi keino palauttaa niitä kadonneita nollia;  (( $(($luku1%$luku2)) > $((10*$q )) )) && apu=$apu'0' on joskus tarpeen ja joskus liikaa
apu2=$(($luku1%$luku2)); luku1=$apu2'0000000000000000000'; luku1=${luku1:0:18} ;echo " "$luku1"  "${#apu2}"  "$apu
done

vanhatmerkit=1
for n in {1..6}; do # kootaan tulosta matriisin palasista
uudetmerkit=${#tulos[$n]}; [[ $uudetmerkit -lt $vanhatmerkit ]] && tulos[$n]='0'${tulos[$n]} ; vanhatmerkit=$uudetmerkit
tulos=$tulos${tulos[$n]}
done
 
[[ ${tulos%%+(0)} -eq 1 ]] && nolliatulosteessa=${nolliatulosteessa:1} # purkkaviritys?
echo "oikea tulos 54 desimaalilla esitetynä on päällä ja alla tulos tästä skriptistä"
bc<<<"scale=${#tulos}; $1/$2" | tr -d '\\\n'; echo ' tämä rivi on bc:stä'
[[ $nolliatulosteessa ]] && echo .$nolliatulosteessa${tulos:0} || echo ${tulos:0:$kokonaisiatulosteessa}.${tulos:$kokonaisiatulosteessa} ;}

# Seuraavia on kokeiltu:
         
time jaa 1233457890123.23 .123456
time jaa .123456 1233457890123.23
time jaa 1 2
time jaa 2 1
time jaa 1 3
time jaa 1 1000
time jaa 1 1001

***

BASH supports fast floating-point calculus via its low-level commands - with low-level commands BASH has more capabilities than most anything.

Litle snippet that operates at least in vanilla Ubuntu ( copy and paste all lines of the code to terminal in one go and press return)(check with bc or such: echo "scale=36; 0.000000000000000001234*1.234567809123456") | bc)(There are still quite many moot points in it - and if they are all corrected you lose easily basic ideas): 
Koodia: [Valitse]
function kerro9 () {
[[ $1 =~ \. ]] && { apu=${1%%[1-9]*}; apu=0${apu##*.}; exp1=${#apu}; luku1=${1//0.$apu/0.} ;} || { apu=${1##*[1-9]}; exp1=-${#apu}; [[ $apu ]] && luku1=${1:0: -$exp1} || luku1=$1 ;}
[[ $2 =~ \. ]] && { apu=${2%%[1-9]*}; apu=${apu##*.}; exp2=${#apu}; luku2=${2//0.$apu/0.} ;} || { apu=${2##*[1-9]}; exp2=-${#apu}; [[ $apu ]] && luku2=${2:0: -$exp2} || luku2=$2 ;}
int1=${luku1%%.*}
int2=${luku2%%.*}
apu2=$(($int1*$int2))
kokonaisia=${#apu2}
tulos=$((10#${luku1//./}*10#${luku2//./}))
tulo=000000000000000000$tulos'00000000000000000000'
tulo=${tulo:0:$((19-$exp1-$exp2))}.${tulo:$((19-$exp1-$exp2))}
apu=${tulo##+(0)}; [[ ${tulo//[^.]/} ]] && apu=${apu%%+(0)}; echo ${apu%.}  ;}

time kerro9 0.000000000000000001234 1.234567809123456

79
Epäilemättä - ja on kiva että kirjoitat mikä muiden taso on sillä se kiinostaa mutta en kerkiä tietoa etsimään.

ja pitää täysin paikkansa että koska BASH:ia ei edes päivitetä kuin kerran vuodessa niin siitä on tullut surkea rääpäle jos se on koskaan varsinaisesti ohjelmointiin sopinutkaan. Ja minä nousen barrikaadeille surkeiden rääpäleiden puolesta - varsinkin kun niitä haukutaan väärin perustein.

Useimmat esittämäni skriptit ovat pelkälle BASH:ille täysin mahdottomia - siis ilman apua bc:ltä, awk:ilta tai joltain muulta. Ja kun kyseessä on jo alkuunsa mahdottomuus niin kuka voi sanoa mihin homma päätyy? Eikö se ole jo aikahyvä barrikaadi? 

Esimerkiksi juuri nyt olen kirjoittamassa logaritmien laskentaa uusiksi. Nopeus nousi yli kymmenkertaiseksi - ja sitäpaitsi se on nimeltäänkin raflaava: logaritmit binäärijärjestelmässä. Vielä uusi kymmenkertaistuminen ja bc alkaa olla vaikeuksissa. Yksinkertaisissa laskuisa se onkin 4 kertaa hitaampi.

***

Logaritmien laskeminen binäärijärjestelmässä:
- tämä skripti ei laske bc:n avulla - siis bc:n voi vaikka pyyhkiä koodista pois skriptin antaman tuloksen muuttumatta.

Logaritmin laskeminen nopeutui 17:sta millisekuntiin. Vaikka se on hidas, on sikäli mielekäs että se näyttää desimaalilaskujen toimivan ja opettaa kuinka logaritmit lasketaan - onhan BASH opetuskieli - kyllä kaikkiin muihinkin laskuihin löytyy montakin erilaista ratkaisumenetelmää - monimutkaisen näköisiä sillä ne ovat matalan-tason koodia mutta itseasiassa ne ovat helppoja. Juuri tämä onkin syy tämän skriptin kehittämiseen sillä se tosiaan osoittaa että BASH:illa on hampaat: miksi virtuoosit uskottelevat että BASH on hampaaton?

Teen tämmöisiä kummajais-skriptejä osoittaakseeni että kyllä BASH desimaalit hallitsee ja on siinäkin suhteessa ihan kelpo kieli prototyyppien tekemistä varten - tämä skripti kertoo lisäksi kuinka logaritmit lasketaan sillä logaritmeilla on kyllä hieman käyttöä edelleenkin. Skripti on kylläkin hidas mutta ei kai kukaan 17:sta millisekunnista hermostu?

Se että logaritmit lasketaan vain luvuista lukualueella 1.xxx....99.xxx... johtuu siitä tosiasiasta että logaritmit määritellään teoriassa aina tieteellisestä esitysmuodosta. Siis luvut saatetaan tälle lukualueelle muuttamalla eksponenttia siten että eksponentti on aina parillinen. Eksponentti ei osallistuu laskuun van ainoastaan määrää logaritmin ensimmäisen numeron. Tuolla perusalueellahan exponentti on nolla joten ensmmäiseen numeroon ei lisätä mitään. Tämä oikealle alueelle saattaminen on päässälasku.

Tämä logaritmien laskemiseen soveltuva skripti ei onnistu ilman nopeaa desimaalilaskentaa.
 
- jos haluat kokeilla toimiiko skripti niin leikkaa-liimaa skriptin jokainen rivi päätteeseen yhdellä kerralla - sillä toimivat skriptit niinkin.

Koodia: [Valitse]
function 2to10 () { # desimaalinen binääriluku (esim. 1.00101) muutetaan kymmenjärjestelmään:
taulu[1]=1;apu=1;for n in {2..60}; do apu=$((2*$apu)); taulu[$n]=$apu; done
apu=${1//./}; apu2=${#apu}
pilkunpaikka=$2
luku=0; for n in $(seq 0 $apu2); do (( ${apu:$n:1} )) && luku=$(($luku+1000000000000000000/${taulu[$n+1]} )); done                                                     
echo ${luku:0:$pilkunpaikka}.${luku:$pilkunpaikka} ;}               

function getlog ()  { # logaritmi määritellään binäärijärjestelmään
kokonaisosa=${1%%.*}; (($kokonaisosa>10)) && pilkunpaikka=1 || pilkunpaikka=0; (($kokonaisosa>100)) && pilkunpaikka=2
[[ ${apu//[^.]/} ]] && vanhalogaritmoitava=$1 || vanhalogaritmoitava=$1".0"
logaritmoitavankokonaisosa=${vanhalogaritmoitava%%.*}
logaritmi=$((${#logaritmoitavankokonaisosa}-1))
until (( ${#logaritmi}>=60 )); do
  apu=${vanhalogaritmoitava//./}; apu=${apu:0:1}.${apu:1}
  luku1=${apu:0:18}
  luku2=${apu:0:18}
  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}
  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}
  luku2=${luku2: -36}
  luku3=${luku3: -36}
  luku4=${luku4: -36}
  luku11=${luku1:0:18}
  luku12=${luku1:18}
  luku21=${luku2:0:18}
  luku22=${luku2:18}
  luku31=${luku3:0:18}
  luku32=${luku3:18}
  luku41=${luku4:0:18}
  luku42=${luku4:18}
  summa1=$((10#$luku12+10#$luku22+10#$luku32+10#$luku42))
  summa1pituus=${#summa1}; ylivuoto=0; (( $summa1pituus>=19 )) && ylivuoto=${summa1:0: -18} && summa1=${summa1:1}
  summa1=000000000000000000$summa1; summa1=${summa1: -18}
  summa2=$((10#$luku11+10#$luku21+10#$luku31+10#$luku41+$ylivuoto))
  apu=$summa2$summa1; (( $desimaaleja )) &&  uusilogaritmoitava=$((10#${apu:0: -$desimaaleja})).${apu: -$desimaaleja} || {  uusilogaritmoitava=$(( 10#$summa2 ))$summa1 ;}
  uudenlogaritmoitavankokonaisosa=${uusilogaritmoitava%%.*}
  logaritmi=$logaritmi$((${#uudenlogaritmoitavankokonaisosa}-1))
  vanhalogaritmoitava=$uusilogaritmoitava
done # tämä päättää se ryhmän joka alkaa käskystä: until
2to10 $logaritmi $pilkunpaikka ;}

logaritmoitava=17.3517; echo -e "\n\nLasketaan logaritmi luvusta: $logaritmoitava\nseuraavalla rivillä varmasti oikea tulos bc:stä vertailua varten ja sitäseuraavalla rivillä tämän skriptin tulos: ";  bc -l<<<"scale=18; l($logaritmoitava)/l(10)";  alkuhetki=$(date +%s.%N); getlog $logaritmoitava; loppuhetki=$(date +%s.%N);echo "kulunut aika mikrosekuntia: "$((${loppuhetki//./}/1000-${alkuhetki//./}/1000))


--- skriptin tuloste on seuraavanlainen:

Lasketaan logaritmi luvusta: 17.3517
seuraavalla rivillä varmasti oikea tulos bc:stä vertailua varten ja sitäseuraavalla rivillä tämän skriptin tulos:
1.239342030392094294
1.239342030392094280
kulunut aika mikrosekuntia: 16866

- tämän skriptin toiminta-aikaa ei voi mitata time-käskyllä sillä sen ja bc:n suhde on kummallinen. Vaan suoritusaika täytyy määritellä sillä tavalla kun se on tässä tehty jotta bc:n poistaminen ei vaikuttaisi ajoitustuloksiin. Sillä tottakai on syytä varmistaa ettei bc vaikuta tämän skriptin toimintaan mitenkään poistamalla lause: bc -l<<<"scale=18; l($logaritmoitava)/l(10)".

- tosin ajoitustavan suurempi tarkkuus on mukavaa sekin - tarkemmat ajat ovat muuten täyttä asiaa ja ihan oikeat vaikka ne heiluvatkin rajusti - nimittäin heiluminen johtuu siitä että Linux suo BASH:lle toiminta-aikaa sillointällöin, vain satunnaisesti ja silloinkin  pienissä palasissa kuin muilta töiltään kerkiää ja hommat tosiaan kestävät mitä milloinkin - tämän heilumisen estämiseksi reaaliaika-käyttöjärjestelmät kehitettiinkin sillä ongelma koskee useimpia ohjelmointi ympäristöjä.

***

Seuraava on liian omituinen käytettäväksi mutta on hyvä tietää että tällainenkin toimintatapa on mahdollinen:

Funktion nimeksi voidaan antaa melkein mitähyvänsä, vaikka merkki: +
- tosin ä:tä ja ö:tä on syytä välttää. Siten voidaan määrätä:

function + () { echo $(($1+$2)) ;}
- matemaattinen merkki + ei ole funktio joten kyseesä ei ole rekursio (eli funktio ei kutsu itseään)

kun tämänjälkeen annetaan käsky: + 1 2
niin se tulostaa 3


mutta vaikka annettaisiin käsky:
funktion + () { echo painu suolle, tänään ei lasketa mitään. ;}
niin echo $((1+2)) tulostaa 3 ihan niinkuin ennenkin ja vain tuo: + 1 2 tulostaisi:  painu suolle, tänään ei lasketa mitään.

---

Samoin voidaan määritellä: function !  () { echo $(($(seq -s* $1))) ;};
mutta käsky : ! 6 ei semmoisenaan viittaa tuohon kertoman määrittelyyn vaan event-määrittelyyn ja siten: ! 20 ei toimi vaan täytyy käskeä: \! 20
- \! 21 menee jo yli lukualueesta.

- kun lopettaa sen pääte-istunon jossa funktio on määritelty unohdetaan sen määrittely. Avattaessa uusi pääte se vanha määrittely palaa voimaan.
 



80
Tavallaan on järkevää pyrkiä eroon kielestä jossa on mielettömästi liikaa käskyjä - ja BASH:issa on. Sillä se ohjelmointi-ympäristö josta yleensä puhutaan on vain yksi sadoista. Senkin käskyt ovat tarkoitettu tiedostojen käsittelyyn ja ovat melkein täysin sopimattomia 'numeronmurskaamiseen' - eikä näin ole alkuunsa ollut joten senkin takia käskyjä on kaksinkertainen määrä.

Aikoinaan luin että BASH:issa on tuhatkunta käskyä ja nän näennäisesti olikin - ja se tuntui ihan hyvältä määrältä. Mutta nopeasti löysin toisista ympäristöistä lisää käskyjä eikä lisäyksille tullut koskaan loppua. Esimerkiksi löysin 'vanhat käskyt' merkkijonojen käsittelyyn ja niitä oli ainakin satoja - mutta niistä ei missään ole minkäänlaista yhteenvetoa, ei käsitystä niiden lukumäärästä, ei tietoa niiden suunnittelu-säännöistä (kyllä joitain perus-sääntöjä on selvinnyt joten niitä voi hyvin rajoitetusti kasata itsekin) eikä niiden olemassaolostakaan yleensä kerrota - tosin jopa BASH-raamatussa on pari mainittuna sivumennen - mutta näiden vanhojen käskyjen hyöty ei valkene ennenkuin niitä on skriptistä suurin osa. Vanhat käskyt toimivat aina samoin vaikka data vaihtuisi paljonkin - mutta uudet käskyt saattavat muuttaa käytöstään kun data muuttuu paljon. Ja vanha käskyt osaavat tehdä asiat samallatavoin muissakin kielissä - esimerkiksi toimitaan kokonaisilla sanoilla eikä merkeillä. Lisäksi ne osaavat hyödyntää yksinkertaisia regex:iä ja ne ovat yli kymmenenkertaa nopeampia kuin uudet käskyt - ja vanhojen käskyjen ryhmät ovat vielä nopeampia mikäli skripti muodostuu yksinomaan noista vanhoista käskyistä.

Esimerkiksi hyvin pieni otos tekstijonojen käsittely-käskyistä:
Koodia: [Valitse]
merkkijono=123; echo "${merkkijono::-${montakolopustapoistetaan:-1}}"  # tulostaa: 12 (tai:echo "${merkkijono%?}" tai:echo "${merkkijono%3}"
merkkijono=12343; echo "${merkkijono%?43}"                             # tulostaa: 12 (tai:echo "${merkkijono%[0-9]43}")
merkkijono=123; echo "${merkkijono: -montakolopusta tulostetaan}"
merkkijono=123; echo "${merkkijono:0:montakoalusta tulostetaan}"       # eli: "${merkkijono:monennestako aloitetaan:kuinka monta}"
merkkijono=123; echo "${merkkijono:montakoalusta poistetaan}"
merkkijono=xabyabz; echo ${merkkijono/ab/acc}    # tulostaa: xaccyabz  -> korvaa vain kerran alusta - korvaava ja korvattava voivat olla eripitkiä
merkkijono=xabyabz; echo ${merkkijono//ab/acc}   # tulostaa: xaccyaccz -> korvaa kaikki
merkkijono='9 8 7 5 4'; echo ${merkkijono//8*5/} # tulostaa: 9 4

merkkijono='9 8 7 5 4'; echo ${merkkijono%% *}   # tulostaa: 9       
merkkijono='9 8 7 5 4'; echo ${merkkijono% *}    # tulostaa: 9 8 7 5
merkkijono='9 8 7 5 4'; echo ${merkkijono##* }   # tulostaa: 4
merkkijono='9 8 7 5 4'; echo ${merkkijono#* }    # tulostaa: 8 7 5 4
- jakopiste voi olla myös sana, muuttuja, erottavien merkkien lista  tai regex  - tai olisikohan taas niin että vain mielikuvitus on rajana. 
- erottavien merkkien lista esimerkiksi - .:/  ->
merkkijono='9:8:7:5:4'; echo ${merkkijono%%[- .:/]*}
merkkijono='9 8 7 5 4'; apu=${merkkijono% * *}; echo ${apu##* } # tulostaa: 7 . Tämmöisiä kannattaa muodostaa ohjelmallisesti.
- näiden käskyjen suurin heikkous on se, että mikäli erottavaa merkkiä ei edes ole tulostetaankin koko sana josta etsitään - eikä tulosteta tyhjää niinkuin pitäisi. Mutta tuohon oikeaan käyttäytymiseen pääsee kun haun eteen kirjoittaa: [[ "$merkkijono" =~ "$jakavasana" ]] && -> esimerkiksi: 
jakavasana=next; merkkijono='9next8next7next5next4'; [[ "$merkkijono" =~ "$jakavasana" ]] && echo ${merkkijono%%"$jakavasana"*} # tulostaa: 9
Ja vaikka merkintä on pitkä on se edelleen nopea; eikä sitä edes kirjoiteta koodiin vaan kopioidaan-liimataan dokumentista.
Joten tästä ryhmästä voidaan muodostaa seuraavat funktiot (siis määritellä uudet käskyt):
otaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%%$erottaja*} ;}
# kutsuesimerkki: otaeka '9 8 7 5 4' tai: otaeka '9 8 7 5 4' erottavasana
#
# siis mikähyvänsä muuttujan:erota merkeistä voi toimi erottajana - ei koko joukko.
# muuttuja:erottavasana sensijaan voi olla tosiaan sana. Sitä ei tarvise kutsussa olla mutta jos se on niin sitä käytetään muutta jos ei ole niin sitten käytetään merkkejä.
# rakenne: if-then-else vanhentui - vastaava merkintä nykyään on tuo tässäkin oleva: [[ $2 ]] && 'joko hypi' || 'tai tärise'. Ja koko ehto on aina samalla rivillä.
## on kommentin lisä-selvennys. Joten ### on seli-seli-seli
## [[ $2 ]] on se if - elikä looginen ehto: onko-olemassa - mikähyvänsä teksti on jotakin joten nollakin on jotakin - ainoastaan '' on ei-mitään
## (( $2 )) olisi matemaattinen if: onko matemaatista arvoa vai onko se nolla. Vain numeroita voi testata näin.
## && on then ja || on else

otavika () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono##*$erottaja} ;}
poistaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono#*$erottaja} ;}
poistavika () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%$erottaja*} ;}
# kutsuesimerkki ja muut kommentit näissä kolmessa ovat samat kuin otaeka:ssa. Nämä yksittäiset käskyt ovat tosi-nopeita.

# Ja näistä saa kasattua käskyn poimijoku:
otaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono%%$erottaja*} ;}
poistaeka () { merkkijono=$1; [[ $2 ]] && erottaja=$2 || erottaja='[- .:/]'; echo ${merkkijono#*$erottaja} ;}
poimijoku () { merkkijono=$1; apu=$1; for (( n=1; n<=$(($2-1)); n++ )); do apu=$(poistaeka "$apu"); done; otaeka "$apu" ;}
# esimerkkikutsu:
poimijoku '987654321 87654321 7654321 54321 4321' 3 # tulee siis 7654321

Hidashan tämmöinen poimijoku on ja turhan monimutkainenkin mutta ajankuluahan sen kokoaminen oli - ja saihan sen tekemisestä paljon oppiakin. Kymmenenkertaa nopeampi ja yksinkertaisempi on:
Koodia: [Valitse]
poimijoku () { IFS="- .:/&¤"; apu=($1); unset IFS; echo ${apu[$2-1]} ;} # IFS (->sananväli joka oletuksena on välilyönti) on aina yksi merkki kerrallaan annetusta listasta - siis tavallaan kokeillaan kunnes joku kelpaa.
time poimijoku '987654321-87654321 7654321.54321:4321/321&21¤1' 8       # siis erottava merkki ovat yleensä sama mutta voivat ne vaihdella etsittävässä vaikka jokakerta.
# mutta IFS on sikäli huono ettei se voi olla sana - ja toisekseenkin leikit IFS:n kanssa menevät joskus pahasti pieleen. Seuraava hyväksyy erotusmerkiksi sanan - mutta se on
# hidas:
poimijoku () { apu=$(echo ${1//$2/' '}); apu=($apu); echo ${apu[$3-1]} ;}
# esimerkkikutsu:
poimijoku '987654321next87654321next7654321next54321next4321' next 3
# apu=($apu) ei ole väkivaltaa sillä jokainen BASH:in tavallinen muuttuja on samalla myös matriisi.
# kohdassa: echo ${apu[$3-1]} lasku: $3-1 on merkinnältään normaali eikä $(($3-1)) niinkui BASH:issa muuten -> sulut tämän aiheuttavat

***

Kirjoitin tämän alkulukujen etsinnän uusiksi saatuani skriptin toiminnasta kokemusta - skripti on sama kuin ennenkin mutta teksti on täysin uutta. Toiminta perustuu merkityksellisimmiltä osiltaan iänikuisen vanhaan C-kieliseen ohjelmaan joten:

1. Tämänpäivän koneille ja ohjelmille tämä on aikalailla vaatimatonta mutta silloin kolmekymmentävuotta sitten ja varsinkin huonoissa laitteessa tämä olisi ollut täysin ihmeellistä - nimittäin tämän kaikki osat ovat noilta ajoilta ja skriptin olisi voinut kasata silloinkin.

2. mikä on BASH:in merkitys sillä se pistää tuon C-kielisen ohjelman toimimaan aikalailla toisin kuin ohjelman tekijät aikoinaan tarkoittivat?

Jättimäisten alkulukujen etsimisessä peruslaskutoimituksetkin vaativat rajoittamatonta tarkkuutta ja usein erittäin edistynyttä matematiikkaa - meidän kannaltamme silkkaa henkimaailman toimintaa ja siihen täytyy löytää valmisohjelma.

Tekijöihin jakavan ohjelman kaltaistakaan ei kukaan pysty tekemään yksin. Ja maailmassa on vain muutama ukko joka saa kasattua toisten tekosia yhteen tarpeeksi monta - siis yksi äänekäs päälläpäsmäri ja useita hiljaisia neroja.

BASH:illakin on merkityksellisiä tehtäviä: se poistaa haettavien joukosta kaikki pienillä alkuluvuilla jaolliset jolloin etsittävien joukko kutistuu kymmenenteen osaansa mikä nopeuttaa toimintaa lähes vastaavasti - nämäkin laskut edellytävät että käytettävissä on rajaton tarkkuus ja laskujen tekeminen kestää silloin kauemmin. BASH myös päättää kuinka kauan yhtä lukua selvitellään sillä tässä käytetty valmisohjelma ei itse tajua sitä tehdä.

Jättikokoisia alkulukuja etsitään tässä ohjelmassa aivan samalla tavalla kuin pieniäkin: luku jaetaan alkutekijöihinsä ja alkulukuja ovat ne joilla luku itse on luvun ainoa tekijä. Normaalisti tämmöinen menetelmä on täysin kahjo etsittäessä tekijöitä jättikokoisista luvuista sillä melkein aina yritys epäonnistuu surkeasti koska useimpien tosisuurten lukujen tekijöihin jakaminen kestää iäisyyksiä.

Mutta tuon ohjelman avulla kannattaaa tehdä tällätavoin sillä ohjelma muodostaa alkuluvun ainoan tekijän erittäin nopeasti.

Joten ei tarvita muuta kuin koettaa onnistuuko jako ja jos se ei onnistu nopeasti niin kyseessä ei ole alkuluku ja voidaan siirtyä tutkimaan seuraavaa lukua. Tosin muutamien muidenkin lukujen tekijät selviävät nopeasti joten kyllä aina joutuu laskemaan myös tekijöiden lukumäärän.

Huonossa läppärissäni tuo tekijöihin jakava factor-ohjelma osaa melkein aina muodostaa alkuluvun ainoan tekijän 0.2 sekunnissa mikäli luku on alle 64 numeroinen ja kolmessa sekunnissa mikäli se on alle 192 numeroinen - ja kunnon koneessa toiminta on kymmeniäkertoja  nopeampaa. 
 
Hakuskripti on sama riippumatta siitä  kuinka suurista luvuista etsitään, mutta muutamia muutoksia sen parametreihin kannattaa tehdä:
timeout:it ovat: .2, .7 ja 3 ja etsintäalueet 5000, 50000 tai 500000 kun etsittävä alkuluku on alle 64 numeroa, 65-128 numeroa ja 129-193 numeroa. Skripti toiminee suuremmillakin numeromäärillä mutta se on toistaiseksi kokeilematta sillä laskenta-aika kasvaa suunnattomasti - esimerkiksi 173 numeroa kestää huonolla koneella päivän. Muuten myös koneen nopeus kannattaa ottaa huomioon: koneen parantuessa timeout pienenee ja hakualue kasvaa.
 
Skripti ja sen esimerkki_kutsu:
Koodia: [Valitse]
function alkuluvun_haku () { echo "Etsin alkulukuja luvun:$1  perästä."; echo -e "\n\nlöytösekunti        alkuluku" > /tmp/delme; pienet_alkuluvut=$( seq 2 200 | factor | awk {' if ($3 =="") {print $1} '} | sed 's/://'); alkuaika=$(awk 'BEGIN {printf "%s\n", systime()}'); time for n in $( seq $1 $(bc<<<$1+5000 | tr -d '\\\n')); do for apu in ${pienet_alkuluvut[*]}; do (( $(bc<<<$n%$apu)==0 )) && n=0 && break ; done; (( $n )) && echo -ne '\rTutkittavana: '$n && timeout .2  factor $n | awk -v alkuaika=$alkuaika {' if ($3 =="") {printf "\r%s\n", systime()-asta luvuitalkuaika"                  "$1}'} | sed 's/://' >> /tmp/delme; done ; cat /tmp/delme | column -t ;}

read -e -p "editoipa lukua josta etsintä aloitetaan: " -i 1234567890123456789012345678901234567890123456789012345678901234567891234567811 luku; alkuluvun_haku $luku


Skriptin toimiessa näytölle kirjoitetaan kokoajan uusia rivejä.  Noilla annetuilla arvoilla toiminta kestää noin 3 minuuttia jonka jälkeen näytölle kirjoitetaan loppuyhteenveto:

löytösekunti  alkuluku
28            1234567890123456789012345678901234567890123456789012345678901234567891234568623
105           1234567890123456789012345678901234567890123456789012345678901234567891234570811
168           1234567890123456789012345678901234567890123456789012345678901234567891234572647


- skriptiä on helppo kokeilla: senkun leikkaat-liimaat skriptin koko koodin yhdelläkertaa päätteeseesi.
ja painat return. Mitään muuta ei saa tehdä paitsi editoida kun pyydetään.

- niitä suuria alkulukuja ei tässä näytetä sillä ne eivät mahdu mihinkään kovinkaan siististi.

- alkulukujen testaamiseksi on verkossa serveri-ohjelma nimeltään: https://www.dcode.fr/primality-test. Sillä kuluu samankokoisen alkuluvun testaamiseen sekunteja - mistäs minä tiedän vaikka se kuluttaisi tuon ajan odotus-loopissa jotta serveriä ei käytettäisi alkulukujen etsintään mutta silti se osoittaa että homma on vaikea isoillekin koneille ja siihen tarkoitetuille ohjelmille.

- ja nyt näppini syyhyävät päästä kunnon koneen näppäimille sillä nopeus vähentää suoritusaikaa tunneista minuutteihin. Saavutukset alkavat olla ikivanhalta leikkikalulta kunnioitettavia?

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