Division in script and floating-point - bash

I would like to do the following operation in my script:
1 - ((m - 20) / 34)
I would like to assign the result of this operation to another variable. I want my script use floating point math. For example, for m = 34:
results = 1 - ((34 - 20) / 34) == 0.588

You could use the bc calculator. It will do arbitrary precision math using decimals (not binary floating point) if you set increease scale from its default of 0:
$ m=34
$ bc <<< "scale = 10; 1 - (($m - 20) / 34)"
.5882352942
The -l option will load the standard math library and default the scale to 20:
$ bc -l <<< "1 - (($m - 20) / 34)"
.58823529411764705883
You can then use printf to format the output, if you so choose:
printf "%.3f\n" "$(bc -l ...)"

Bash does not do floating point math. You can use awk or bc to handle this. Here is an awk example:
$ m=34; awk -v m=$m 'BEGIN { print 1 - ((m - 20) / 34) }'
0.588235
To assign the output to a variable:
var=$(awk -v m=$m 'BEGIN { print 1 - ((m - 20) / 34) }')

Teach bash e.g. integer division with floating point results:
#!/bin/bash
div () # Arguments: dividend and divisor
{
if [ $2 -eq 0 ]; then echo division by 0; return 1; fi
local p=12 # precision
local c=${c:-0} # precision counter
local d=. # decimal separator
local r=$(($1/$2)); echo -n $r # result of division
local m=$(($r*$2))
[ $c -eq 0 ] && [ $m -ne $1 ] && echo -n $d
[ $1 -eq $m ] || [ $c -eq $p ] && echo && return
local e=$(($1-$m))
c=$(($c+1))
div $(($e*10)) $2
}
result=$(div 1080 633) # write to variable
echo $result
result=$(div 7 34)
echo $result
result=$(div 8 32)
echo $result
result=$(div 246891510 2)
echo $result
result=$(div 5000000 177)
echo $result
Output:
1.706161137440
0.205882352941
0.25
123445755
28248.587570621468

echo $a/$b|bc -l
gives the result.
Example:
read a b
echo $a/$b|bc -l
Enter a & b value as 10 3, you get 3.3333333333
If you want to store the value in another variable then use the code
read a b
c=`echo $a/$b|bc -l`
echo $c
It also gives the same result as above.
Try it...

I know this is an old thread, but this seemed like a fun project to tackle without using bc or invoking recursion. I'm sure it can be improved, but this maxed out my skill.
numerator=5
denominator=7 # - 0 -> returns "undef"
decimal_places=4 # - 0 -> same as echo $(( $numerator / $denominator ))
_result_sign=""
let _dp_exp=10**decimal_places
if [ $denominator -eq 0 ]; then _div_result_int_large=0; else let _div_result_int_large=$((numerator * _dp_exp / denominator)); fi
if [ $_div_result_int_large -lt 0 ]; then let _div_result_int_large=$(( _div_result_int_large * -1 )); _result_sign="-"; fi
let _div_result_int=$((_div_result_int_large / _dp_exp))
let _div_result_mant=$((_div_result_int_large - _div_result_int * _dp_exp))
let _dp_lzeros=$((decimal_places - ${#_div_result_mant}))
printf -v _div_result_mant_padded "%.${_dp_lzeros}d$_div_result_mant"
div_result="$_result_sign$_div_result_int"
if [ $decimal_places -gt 0 ]; then div_result="$_result_sign$_div_result_int.$_div_result_mant_padded"; fi
if [ $denominator -eq 0 ]; then div_result="undef"; fi
echo $div_result
Example output:
numerator=5
denominator=7
decimal_places=5
-> 0.71428
numerator=250
denominator=13
decimal_places=0
-> 19
numerator=-5
denominator=6
decimal_places=2
-> -0.83
numerator=3
denominator=0 # - uh-oh
decimal_places=2 # - can be anything, in this case
-> undef

Use this script open this file with favorite editor like:
$ sudo vim /usr/bin/div
Then paste this code:
#!/bin/bash
# Author: Danial Rikhteh Garan (danial.rikhtehgaran#gmail.com)
if [[ -z "$1" ]] || [[ -z "$2" ]]; then
echo "Please input two number"
echo "for 100/50 use: div 10 50"
exit 1;
fi
div=$(echo "$1/$2" | bc -l);
echo 0$div | sed 's/[0]*$//g'
Now chmod it to 755:
$ sudo chmod 755 /usr/bin/div
Now use it:
$ div 5 100
0.05
In your script you can use this:
var=$(div 5 100);
echo "$var"

Related

shell - iterating a loop for a (validated) supplied number or range of numbers

I need to accept input from user (i.e. 'read').
This input can be either a single positive number or a range of numbers (in the form X-Y ).
I then need to validate this input and perform an iterative loop through the range of numbers (or just once in the case of only X).
examples:
1) User supplies: "8" or "8-"
Loop runs only a single time supplying the number 8
2) User supplies: "13-22"
Loop runs 11 times (13 through 22) referencing the number 13.
3) User supplies: "22-13"
Probably should behave like #2 above...but I'm open to other clean ways to validate the input.
I have worked on the following so far, which isn't very clean, complete, or even 100% correct for what I was asking - but it shows the effort and idea I'm going for:
echo "line?"; read -r deleteline
case "$deleteline" in
''|*[!0-9\-]*) echo "not a number";;
[1-9]|[1-9][0-9]*);;
esac
deleteline_lb=$(echo $deleteline|awk -F "-" '{print $1}')
deleteline_ub=$(echo $deleteline|awk -F "-" '{print $2}')
if [ ! $deleteline_lb = "" ] && [ ! "$deleteline_ub" = "" ]; then
delete_line_count=1
delete_line_count=$(expr $deleteline_ub - $deleteline_lb)
if [ $delete_line_count -le 0 ]; then
delete_line_count=1
fi
fi
i=1; while [ $i -le $delete_line_count ]; do
echo $deleteline_lb $i
i=$(($i + 1))
done
This needs to run in sh, things like seq are not supported - so stick with posix compliant methods...
To clarify I am looking to do the following (pseudo-code):
1) accept input from user
2) validate if input is in the form "#" or "#-#" (range).
3) Execute chosen (arbitrary) code path based on proper/improper input.
4) If single # is given then store that to variable to perform future operations against.
5) If range is given, store both numbers in variable to be able to perform the operation against the lower # up to the higher number. More specifically it would be "(higher #) - (lower #) + 1". So if range were 12-17 then we need to perform operation against 12, 6x. (17 - 12 + 1). IOW, 12-17 inclusive.
6) A way to easily denote if data set is range vs single number is also desired so that code path to each can be easily branched.
thanks for helping!
UPDATE:
Using my basic code I reworked it (with a bit of input from a friend), and basically came up with this:
while true;do
printf "\\delete lines? [e=Exit] ";read -r deleteline
case "$deleteline" in
[Ee]) break;;
*)
echo "$deleteline" | egrep -q '^[[:digit:]-]*$'
if [ $? -ne 0 ]; then
printf "\\n input is not a number.\\n"
else
delete_range_start=`echo $deleteline|awk -F "-" '{print $1}'`
delete_range_end=`echo $deleteline|awk -F "-" '{print $2}'`
if [ $delete_range_end -lt $delete_range_start ]; then
printf "\\n upper range must be higher than lower range.\\n"
else
if [ "$delete_range_end" = "" ]; then
delete_range_end=$delete_range_start
elif [ $delete_range_end -gt $lineNumbers ]; then
printf "\\Setting range to last entry\\n"
fi
break
fi
fi
;;
esac
done
deleteline=$delete_range_start
deleteloop=`expr $delete_range_end - $delete_range_start + 1`
i=1
while [ $i -le $deleteloop ]; do
# Insert all processing code in here
i=`expr $i + 1`
done
If you have a posix compliant awk, try this:
echo "$userInput" | awk -F- '
($1+0==$1&&$2+0==$2){
for(i=$1;($1<$2?i<=$2:i>=$2);)
print ($1<$2?i++:i--);
next
}
$1+0==$1{
print $1;
next
}
$2+0==$2{
print $2;
next
}
($1+0!=$1&&$2+0!=$2){
exit 1
}'
The script check if the 2 fields (separated with -) are numbers. If so, it prints these numbers in an ascending or descending way depending if the first number is greater or lower than the second one.
If only one input, the script just prints it.
If none of the field are number, it exits with a non zero value.
This script could be the validation step of a shell script like this:
$ cat test.sh
#!/bin/sh
echo -n "range: "
read -r range
validated_input=$(echo "$range" | awk -F- '($1+0==$1&&$2+0==$2){for(i=$1;($1<$2?i<=$2:i>=$2);)print ($1<$2?i++:i--);next}$1+0==$1{print $1;next}$2+0==$2{print $2;next}($1+0!=$1&&$2+0!=$2){exit 1}')
if [ $? -ne 0 ]; then
echo "Incorrect range" >&2
fi
for i in $validated_input; do
echo "$i"
done
Examples:
$ ./test.sh
range: 10-6
10
9
8
7
6
$ ./test.sh
range: 8-
8
$ ./test.sh
range: hello
Incorrect range

Bash syntax errors like `syntax error: operand expected (error token is "{1 % 2 ")`

Please tell why printing odd numbers in bash script with the following code gives the error:
line 3: {1 % 2 : syntax error: operand expected (error token is "{1 % 2 ")
for i in {1 to 99}
do
rem=$(( $i % 2 ))
if [$rem -neq 0];
then
echo $i
fi
done
This is working example:
for i in {1..99}
do
rem=$(($i % 2))
if [ "$rem" -ne "0" ]; then
echo $i
fi
done
used for loop have a typo in minimum and maximum number, should be {1..99} instead of {1 to 99}
brackets of the if statement needs to be separated with whitespace character on the left and on the right side
Comparision is done with ne instead of neq, see this reference.
As already pointed out, you can use this shell checker if you need some clarification of the error you get.
Not really sure why nobody included it, but this works for me and is simpler than the other 'for' solutions:
for (( i = 1; i < 100; i=i+2 )); do echo $i ; done
To print odd numbers between 1 to 99
seq 1 99 | sed -n 'p;n'
With GNU seq, credit to gniourf-gniourf
seq 1 2 99
Example
$ seq 1 10 | sed -n 'p;n'
1
3
5
7
9
if you reverse it will print even
$ seq 1 10 | sed -n 'n;p'
2
4
6
8
10
One liner:
for odd in {1..99..2}; do echo "${odd}"; done
Or print in a cluster.
for odd in {1..99..2}; do echo -n " ${odd} "; done
Likewise, to print even numbers only:
for even in {2..100..2}; do echo "${even}"; done
OR
for even in {2..100..2}; do echo -n " ${even} "; done
Replace {1 to 99} by {1..99}.
for (( i=1; i<=100; i++ ))
do
((b = $i % 2))
if [ $b -ne 0 ]
then
echo $i
fi
done
for i in {1..99}
do
rem=`expr $i % 2`
if [ $rem == 1 ]
then
echo "$i"
fi
done
for i in {0..49}
do
echo $(($i*2+1))
done

Whats wrong with the function params and the comparison condition?

im figuring out how to get this working..
Im not very experienced with Bin Bash,
but I have this script:
The variables.cfg contains:
pvp="0400 1200 2000"
#!/bin/sh
CONFIG_FILE="/home/nacion/variables.cfg"
source $CONFIG_FILE
#Arreglar tiempo
ATM (){
if [ $((1+2)) -gt 24 ];
then
$a=$1 + $2
$b=$a-2400
echo $b
fi
}
declare -a VcHorarios=($pvp)
for HoraAR in "${VcHorarios[#]}"
do
I dont know why is not passing the param
ES=$(ATM $HoraAR 500)
HorarioSv="&b&lMX $((HoraAR-300)) - AR $HoraAR H - ES $ES H"
done
echo $HorarioSv
The result:
[root#anda ~]# /home/nacion/vcprueba.sh
&b&lMX 1700 - AR 2000 H - ES H
[root#ns500347 ~]#
Thanks in advance for your help
You can drop the $ from variable names inside $((...)). $1 and $2 are not variables; they are positional parameters.
if [ $(($1 + $2)) -gt 24 ];
Once you manage to enter the body of the if statement, the correct commands are
a=$(( $1 + $2 ))
b=$(( a - 2400 )) # or $(( $a - 2400 )), if you prefer

Bash modulo strange result (not negative)

I want to use modulo in a Bash script, but the result is always to low.
I need something like this: days = jdn mod p
this is what i have:
#!/bin/bash
if [ $# -ne 2 ]
then
echo "Fout, syntaxis: $0 maand(in cijfers) jaar"
exit 1
fi
a=$(echo "14 - $1"|bc)
y=$(echo "$2 + 4800 - $a"|bc)
m=$(echo "12 * $a - 3 + $1"|bc)
jdn=$(echo "scale=2;1 + (153 * $m +2)/5 + 365 * $y +${y}/4 - ${y}/100 - 32045"|bc)
jdn=$(echo "$jdn + 1"|bc|sed "s/...$//")
d=1
p=$(echo "29.530588853")
while [ "$d" != "32" ]
do
echo -n "$d"
days=$(echo "scale=2;${jdn} % ${p}"|bc)
fase=$(echo "scale=2;$p / $days"|bc)
fase1=$(echo "${fase}<7.382647213"|bc)
if [ $fase1 -eq 1 ]
then
echo -n "#"
elif [ $(echo "${fase}<14.76529443"|bc) -eq 1 ]
then
echo -n ")"
elif [ $(echo "$fase<22.14794164"|bc) -eq 1 ]
then
echo -n "0"
else
echo -n "("
fi
d=$(echo "$d + 1"|bc)
jdn=$(echo "$jdn +1"|bc)
done
jdn has as result 2455369, days should be 9.55 but the result is 0.054...
Arithmetic in Bash shell is done with integers only, so it is better to change the approach (or multiply the qantities by a large power of ten to make them be integers).
When you are doing math with integers in Bash, include the arithmetic in double parentheses, or to use a 'let' keyword.
((${jdn} % ${p}))
as described in these sources.
http://ubuntuforums.org/showthread.php?t=1970559
How to use mod operator in bash?
I've found a solution: without scale, it works fine. So this is my code:
days=$(echo "${jdn} % ${p}"|bc)
I only had to make sure that the modulo did'nt give a 0 back and than i could set scale in the division. It seemed to be more a math question at the end...

slow running script. How can I increase its speed?

How can I speed this up? it's taking about 5 minutes to make one file...
it runs correctly, but I have a little more than 100000 files to make.
Is my implementation of awk or sed slowing it down? I could break it down into several smaller loops and run it on multiple processors but one script is much easier.
#!/bin/zsh
#1000 configs per file
alpha=( a b c d e f g h i j k l m n o p q r s t u v w x y z )
m=1000 # number of configs per file
t=1 #file number
for (( i=1; i<=4; i++ )); do
for (( j=i; j<=26; j++ )); do
input="arc"${alpha[$i]}${alpha[$j]}
n=1 #line number
#length=`sed -n ${n}p $input| awk '{printf("%d",$1)}'`
#(( length= $length + 1 ))
length=644
for ((k=1; k<=$m; k++ )); do
echo "$hmbi" >> ~/Glycine_Tinker/configs/config$t.in
echo "jobtype = energy" >> ~/Glycine_Tinker/configs/config$t.in
echo "analyze_only = false" >> ~/Glycine_Tinker/configs/config$t.in
echo "qm_path = qm_$t" >> ~/Glycine_Tinker/configs/config$t.in
echo "mm_path = aiff_$t" >> ~/Glycine_Tinker/configs/config$t.in
cat head.in >> ~/Glycine_Tinker/configs/config$t.in
water=4
echo $k
for (( l=1; l<=$length; l++ )); do
natom=`sed -n ${n}p $input| awk '{printf("%d",$1)}'`
number=`sed -n ${n}p $input| awk '{printf("%d",$6)}'`
if [[ $natom -gt 10 && $number -gt 0 ]]; then
symbol=`sed -n ${n}p $input| awk '{printf("%s",$2)}'`
x=`sed -n ${n}p $input| awk '{printf("%.10f",$3)}'`
y=`sed -n ${n}p $input| awk '{printf("%.10f",$4)}'`
z=`sed -n ${n}p $input| awk '{printf("%.10f",$5)}'`
if [[ $water -eq 4 ]]; then
echo "--" >> ~/Glycine_Tinker/configs/config$t.in
echo "0 1 0.4638" >> ~/Glycine_Tinker/configs/config$t.in
water=1
fi
echo "$symbol $x $y $z" >> ~/Glycine_Tinker/configs/config$t.in
(( water= $water + 1 ))
fi
(( n= $n + 1 ))
done
cat tail.in >> ~/Glycine_Tinker/configs/config$t.in
(( t= $t + 1 ))
done
done
done
One thing that is going to be killing you here is the sheer number of processes being created. Especially when they are doing the exact same thing.
Consider doing the sed -n ${n}p $input once per loop iteration.
Also consider doing the equivalent of awk as a shell array assignment, then accessing the individual elements.
With these two things you should be able to get the 12 or so processes (and the shell invocation via back quotes) down to a single shell invocation and the backquote.
Obviously, Ed's advice is far preferable, but if you don't want to follow that, I had a couple of thoughts...
Thought 1
Rather than run echo 5 times and cat head.in onto the Glycine file, each of which causes the file to be opened, seeked (or sought maybe) to the end, and appended, you could do that in one go like this:
# Instead of
hmbi=3
echo "$hmbi" >> ~/Glycine_thing
echo "jobtype = energy" >> ~/Glycine_thing
echo "somethingelse" >> ~/Glycine_thing
echo ... >> ~/Glycine_thing
echo ... >> ~/Glycine_thing
cat ... >> ~/Glycine_thing
# Try this
{
echo "$hmbi"
echo "jobtype = energy"
echo "somethingelse"
echo
echo
cat head.in
} >> ~/Glycine_thing
# Or, better still, this
echo -e "$hmbi\njobtype = energy\nsomethingelse" >> Glycine_thing
# Or, use a here-document, as suggested by #mklement0
cat -<<EOF >>Glycine
$hmbi
jobtype = energy
next thing
EOF
Thought 2
Rather than invoke sed and awk 5 times to find 5 parameters, just let awk do what sed was doing, and also do all 5 things in one go:
read symbol x y z < <(awk '...{printf "%.10f %.10f %.10f" $2,$3,$4}' $input)

Resources