#!/bin/bash
# petteriIII 10.6.2016 ~/OMATSKRIPTIT/FUNKTIOKIRJASTO/matemaattiset_funktiot   http://forum.ubuntu-fi.org/index.php?topic=47408.msg364003#top 26
set +H # jos tämä puuttuisi niin kertoman merkki: ! aiheuttaisi  virheen mikäli huutomerkki ei ole lauseen viimeinen merkki
unset command_not_found_handle # poistaa huomautukset mallia: tarkoititko käskyä xxx paketissa yyy
export BC_LINE_LENGTH=0 # jotta bc tulostaisi koko tulosteen yhtenä pötkönä; elikä ilman kenoja
 
trap 'laske "$BASH_COMMAND"' 1 ERR  

function laske () { # Tuloparametrina on laskukaava. Tulostaa näytölle ja asettaa kutsuttujen funktioiden nimiset muuttujat ja tuloksen muuttujaan: tulos.
stty sane # <ctrl+j>stty sane<ctrl+j> # echo <ctrl-v><esc>c<enter> # reset
[[ ! "$1" ]] && echo "- laskuissa on 66 desimaalia jollei toisin määrätä. Desimaalipisteen jälkeisen osan peränollia ei tulosteta.
- kokonaisosan numeromäärä on aina rajoittamaton ja desimaalien määrän voi määrätä ihan miksi haluaa.
- sallitut operaattorit: +, -, *, / ja ^ Näissä laskuissa ^ on potenssiinkorotus, esimerkiksi 3^0.5  tai: 10000000000^-0.1.   
- funktiot: sin, asin, sinh, cos, acos, cosh, tan, atan, tanh, sqrt, log, ln, exp, abs, int, e, pi, !, kahteen, kymmeneen, des (desimaaliluku),  rad
- merkitätavat noudattavat normaaleja matematiikan sääntöjä. Sulkuina on kuitenkin helpointa käyttää hakasulkuja. 
- syötettävien esitysmuoto voi olla tieteellinenkin, esimerkiksi: 1.23e6 .
- desimaalien ja radiaanien määräämis-esimerkki: des256radsin0.511 . Asetukset säilyvät kunnes muutetaan tai vaihdetaan päätettä
- testilaskuja: -1-[[2*6!+1]/1.441e3]+[[sin210-cos60]*tan60*2]/[3^.5]*ln[e^-pi]/pi -> 0 ; des256ln[e^ln[e^ln[e^ln[e^ln[e^ln[e^ln[e^ln[e^-pi]]]]]]]]+pi -> 0;
  2+1.1e2+e-2.7 -> 112.018....; sin[asinx]-[asin[sinx]] -> 0 kun x=-1...1,  des256 [6^-6^6.1^6^-5.9^6^6]-[6^-6^6.1^6^-5.9^6^6] -> 0; 10000!
- edellisen laskun tulokseen käänteisluku tulostuu kun antaa laskuksi pelkän 1:n" && return

[[ "$1" = 1 ]] && tulos='1/'$tulos || tulos="$1"
[[ $(tr -dc '\[' <<<"$tulos" | awk '{ print length; }') -ne $(tr -dc '\]' <<<"$tulos" | awk '{ print length; }') ]] && echo 'sulut epätasapainossa' && return
[[ -z $sulkujaratkaisematta ]] && {
sulkujaratkaisematta=1
 
lasketaan_radiaaneissa=${lasketaan_radiaaneissa:-0} # toimintamuotona on asteet ellei toimintamuodoksi peritä radiaaneja.
desimaaleja=$(echo $tulos | grep -Po '(?<=des)[0-9]*' ) && tulos=${tulos//des$desimaaleja} # koe
desimaaleja=${desimaaleja:-66} # laskujen desimaaliluku on 66 ellei peritä toista arvoa.

tulos=${tulos#+}    # poistetaan laskun edestä merkki + jos sellainen on
tulos=${tulos%% #*} # poistetaan peräkommentit - peräkommentin edessä on aina välilyönti
tulos=$(echo $tulos | tr -d \' | tr "(" '[' | tr ")" ']' ) # poistetaan heittomerkki ja muunnetaan kaarisulut hakasuluiksi.
tulos=${tulos//++/+}; tulos=${tulos//\*+/\*}; tulos=${tulos//\/+/\/}; tulos=${tulos//\^+/\^}; tulos=${tulos//\*\*/\^};


[[ $(echo $tulos | grep rad) ]] && lasketaan_radiaaneissa=1 && tulos=${tulos//rad}   

# muutetaan luonnollisen järjestelmän kantaluku e lukuarvoonsa kaikkialla. Siis luvut kuten tuo e kaavassa: 1+e^2
e_numeroina=$(echo "scale=$desimaaleja; e(1)" | bc -l)
tulos=$(echo $tulos | sed "s/\be\b/$e_numeroina/g")
 
# muutetaan pi (ympyrän kehä/halkaisija) lukuarvoonsa kaikkialla
pi_numeroina=$(echo "scale=256; 4*a(1)" | bc -l)
tulos=$(echo $tulos | sed "s/\bpi\b/$pi_numeroina/g") # oli tulos=$(echo $tulos | sed "s/[+*^/-]pi\b/&$pi_numeroina/g" | sed "s/pi$pi_numeroina/$pi_numeroina/g")
} # alku-sulku rivillä 25 elikä rivillä jolla on tksti: [[ -z sulkujaratkaisematta ]] && {                             

# muuta kaikki tieteelliset esitysmuodot numeroiksi heti kun "eksponentti" on ratkaistu (niinkuin: 1.23e5 -> 123000)
muuta_tieteelliset_esitystykset_numeroiksi

tput cuu1; tput el; echo # kursori ylös rivinverran ja rivin tyhjennys

etsi_laskukaavasta_ratkaisukelpoinen_sulku_ja_ratkaise_se

for funktiolaji in $(echo $tulos | tr [0-9/*+-^] " " ); do erota_sulun_seuraavasta_funktiosta_parametrit_ja_kutsu_funktiota $funktiolaji ; done

# ratkaise x^y kaikkialla missä x ja y on ratkaistu numeroiksi; siis sulut ratkottu
apu=$(echo $tulos | grep -o [0-9.][0-9.]*^[0-9.+-][0-9.]*) # | tr "\n" " " | awk '{print $1}')
while [[  $apu != "" ]] ; do ratkaise_eksponentti $apu; apu=$(echo $tulos | grep -o [0-9.][0-9.]*^[0-9.+-][0-9.]*); done
# mikä on seuraavan lauseen merkitys ? Laskee tuloksen silloin kun jäljellä on enää numeroita?
[[ $(echo $tulos | tr -d \.[0-9]) ]] && tulos=${tulos//--/+} && tulos=$(echo "obase=10;ibase=10;scale=$desimaaleja ; $tulos" | bc -l) && [[ $vanha_tulos != $tulos ]] &&  tulosta $tulos && vanha_tulos=$tulos
[[ "${#FUNCNAME[@]}" = 1 ]] && unset vanha_tulos && unset sulkujaratkaisematta && siisti_lopputulos # viimeisellä kierroksella funktio-pinossa on yksi jäsen ja tulos voidaan pyöristää
}

function siisti_lopputulos () { # vähän niinkuin pyöristys
[[ ! $(echo $tulos | grep [\.]) ]] && echo $tulos && return # mikäli luvussa ei ole desimaaleja ei sitä saa siistiä
[[ ! $(echo $tulos | tr -d [+-.0]) ]] && echo 0 && return # pätevä? testi siitä onko joku nolla
[[ ${tulos:0:1} = '-' ]] && tulos=$(echo "scale=$desimaaleja; $tulos-10^-($desimaaleja-3)" | bc -l) || tulos=$(echo "scale=$desimaaleja; $tulos+10^-($desimaaleja-4)" | bc -l)
echo; echo ${tulos:0:desimaaleja-4} | sed 's/0*$//g;s/^\.$/0/g;s/\.$//g;s/-$/0/g'
}

function erota_sulun_seuraavasta_funktiosta_parametrit_ja_kutsu_funktiota () {
[[ ! $(declare | grep $funktiolaji' \(\)') ]] && echo tuntematon funktio && return
[[ $funktiolaji = ! ]] && apu=$(echo ${tulos%%\!*}  | sed 's/\^/ /g;s/\*/ /g;s/\// /g;s/\+/ /g;s/\-/ /g') && numero=${apu##* }
[[ $funktiolaji != ! ]] && apu=$(echo ${tulos#*$funktiolaji} | sed  's/\^/ /g;s/\*/ /g;s/\// /g;s/\+/ /g;s/\-/ /g') && numero=${apu%% *}
[[ $numero = "" ]] && apuu=$(echo ${tulos#*$funktiolaji}) && apu=${apuu:0:1}${apu:1} && numero=${apu%% *} # palautetaan operaattoreita poistettaessa hävinnyt merkki
kutsu="$funktiolaji $numero"; arvo=$($kutsu); korvattava=$funktiolaji$numero;[[ $funktiolaji = ! ]] && korvattava=$numero$funktiolaji;tulos=$(echo $tulos | sed "s/$korvattava/$arvo/g")
}

function tulosta () { #  mikäli luvussa on desimaalipiste niin poistetaan peränollat ja pistekin jos se jää loppuun yksikseen
vanha_tulos=$1; [[ $vanha_tulos != $1 ]] && return
[[ ${1%%\.*} != $1 ]] && echo $1 | sed 's/0*$//g;s/\.$//g' || echo $1
}

function ratkaise_eksponentti () { # kokonais-exsponenteille bc käyttää tarkkaa eksponentaatiota mutta se ei hyväksy desimaalisia exponentteja
# tässä desimaali_exponentit ohjataan käyttämään niille soveltuvaa hieman epätarkkaa desimaali-eksponentiaatiota.
kantaluku=${1%%^*} 
exponentti=${1##*^}
[[ $(echo $exponentti | grep "\." ) ]] && valitulos=$(echo "scale=$desimaaleja; e($exponentti*l($kantaluku))" | bc -l) || valitulos=$(echo "scale=$desimaaleja; $kantaluku^$exponentti" | bc -l)
korvattava=$kantaluku"\^"$exponentti; tulos=${tulos//$korvattava/$valitulos}   
tulosta $tulos
}
 
function etsi_laskukaavasta_ratkaisukelpoinen_sulku_ja_ratkaise_se  () {
# raja=112\101\116\116\101\114\105\73\73\73
while [[ $tulos != ${tulos#*\[} ]]; do
ilman_sulkuja=$(echo $tulos | sed 's/\].*//g;s/.*\[//g') # tämä lause etsii ratkaisukelpoisen sulun
apu=$(echo $tulos | grep -Fob -- $ilman_sulkuja)
monennestako_merkista=${apu%%\:*}
monenteenko_merkkiin=$(echo $monennestako_merkista+${#ilman_sulkuja} | bc )
tulosta $tulos
tulos_alussa="$tulos" 
laske $ilman_sulkuja
tulos=${tulos_alussa:0:$monennestako_merkista-1}$tulos${tulos_alussa:$monenteenko_merkkiin+1}; echo $tulos
# muuta_tieteelliset_esitystykset_numeroiksi # tarvitaanko tässäkohtaa?
done
}

function muuta_tieteelliset_esitystykset_numeroiksi() { # ratkaistaan tieteellinen esitystapa; luvut mallia 1.23e18
apu=$(echo $tulos | grep -o  [.0-9]*e[.0-9+-]* | tr "\n" " " | tr -d '" "\\' | awk '{print $1}')
while [[ $apu ]] ; do arvo=$(echo $apu | sed "s/e/\*10\^/g" | bc -l); tulos=${tulos//$apu/$arvo} ; apu=$(echo $tulos | grep -o  [.0-9]*e[0-9+-]* | tr -d "\\\n" | tr -d '' | awk '{print $1}'); done 
}

function tan () {
asteluku=$(echo ${1#+}) # jos asteluvun edessä on + niin  poistetaan se
[[ $lasketaan_radiaaneissa -eq 1 ]] && tan=$(echo "scale=$desimaaleja; s($asteluku)/c($asteluku)" | bc -l | tr -d "' '\\" | tr -d "\n") || tan=$(echo "scale=$desimaaleja; s($asteluku*$pi_numeroina/180)/c($asteluku*$pi_numeroina/180)" | bc -l)  ; tulosta $tan
}

function kahteen () {
kahteen=$(echo "obase=2;ibase=10;$1" | bc -l) ; tulosta $kahteen
}

function kymppiin () {
kymppiin=$(echo "obase=10;ibase=2;$1" | bc -l) ; tulosta $kymppiin
}

function cos () {
asteluku=$(echo ${1#+}) # jos asteluvun edessä on + niin  poistetaan se
[[ $lasketaan_radiaaneissa -eq 1 ]] && cos=$(echo "scale=$desimaaleja; c($asteluku)" | bc -l) || cos=$(echo "scale=$desimaaleja; c($asteluku*$pi_numeroina/180)" | bc -l) ; tulosta $cos
}

function log () { # briggsin logaritmi
[[ $1 -eq 1 ]] && log=0 || log=$(echo "scale=$desimaaleja; l($1)/l(10)" | bc -l); tulosta $log;
}

function ln () { # e-kantainen logaritmi
ln=$(echo "scale=$desimaaleja; l($1)" | bc -l); tulosta $ln 
}

function sqrt () { # neliöjuuri
[[ $1 -lt 0 ]] && echo "määrätty ottamaan neliöjuuri negatiivisesta luvusta: $1 joten lopetan" &&  kill -9
sqrt=$(echo "scale=$desimaaleja; sqrt ($1)" | bc -l); tulosta $sqrt
}

function ! () {
[[ $1 -eq 0 ]] && kertoma=1 || kertoma=$(seq -s* $1 | bc | tr -d '\\\n'); tulosta $kertoma
}

function atan () { # arctan
[[ $lasketaan_radiaaneissa -eq 1 ]] && atan=$(echo "scale=$desimaaleja; a($1)" | bc -l) || atan=$(echo "scale=$desimaaleja; a($1*$pi_numeroina/180)" | bc -l) ; tulosta $atan
}

function sin () {
asteluku=$(echo ${1#+}) # jos asteluvun edessä on + niin  poistetaan se
[[ ${lasketaan_radiaaneissa:-1} -eq 1 ]] && sin=$(echo "scale=${desimaaleja:-66}; s($asteluku)" | bc -l) || sin=$(echo "scale=${desimaaleja:-66}; s($asteluku*$pi_numeroina/180)" | bc -l); tulosta $sin # miksei tulos saa uutta arvoa ?
}

function acos () { # arccos # lasku on: pi/2-asin, rajuja muutoksia
viesti='acos on määritelty vain välillä -1...+1. otetaan arvoksi:'
[[ $(echo $1 '>=' 1 | bc -l) -eq 1 ]] && acos=0 && echo -n $viesti && tulosta $acos && return
[[ $(echo $1 '<=' -1 | bc -l) -eq 1 ]] && acos=1 && echo -n $viesti && tulosta $acos && return
[[ $lasketaan_radiaaneissa -eq 1 ]] && acos=$(echo "scale=${desimaaleja:-66}; $pi_numeroina/2-a($apu/sqrt(1-($1*$1)))" | bc -l) || acos=$(echo "scale=${desimaaleja:-66}; 180/$pi_numeroina*($pi_numeroina/2-a($1/sqrt(1-($1*$1))))" | bc -l) 
tulosta $acos
}

function asin () { # arcsini, rajuja muutoksia
viesti='asin on määritelty vain välillä -1...+1. otetaan arvoksi:'
[[ $(echo $1 '>=' 1 | bc -l) -eq 1 ]] && acos=0 && echo -n $viesti && tulosta $acos && return
[[ $(echo $1 '<=' -1 | bc -l) -eq 1 ]] && acos=1 && echo -n $viesti && tulosta $acos && return
[[ $lasketaan_radiaaneissa -eq 1 ]] && asin=$(echo "scale=${desimaaleja:-66}; a($1/sqrt(1-($1*$1)))" | bc -l) || asin=$(echo "scale=${desimaaleja:-66}; a($1/sqrt(1-($1*$1)))*180/$pi_numeroina" | bc -l) 
tulosta $asin
}

function sinh () { # korvauskaava on: ( ex - e-x )/2
sinh=$(echo "scale=${desimaaleja:-66}; ($e_numeroina^$1-$e_numeroina^-$1)/2" | bc -l);tulosta $sinh
}

function cosh () { # korvauskaava on: ( e x + e -x )/2
cosh=$(echo "scale=${desimaaleja:-66}; ($e_numeroina^$1+$e_numeroina^-$1)/2" | bc -l) ; tulosta $cosh
}

function tanh () { # korvauskaava on: ( ex - e-x )/( ex + e-x )
tanh=$(echo "scale=${desimaaleja:-66};  ($e_numeroina^$1-$e_numeroina^-$1)/($e_numeroina^$1+$e_numeroina^-$1)" | bc -l) ; tulosta $tanh
}

function abs () { # tämä poistaa etumerkin, myös plussan
abs=$1; abs=$(echo ${abs#[-+]}); tulosta $abs
}

function int () { # muuttujan kokonaisluku-osa
int=$(echo 0$1 | cut -d. -f1 ); tulosta $int
}
