Floating point results in Bash integer division
Sebastian Wright
I have a backup script on my server which does cron jobs of backups, and sends me a summary of files backed up, including the size of the new backup file. As part of the script, I'd like to divide the final size of the file by (1024^3) to get the file size in GB, from the file size in bytes.
Since bash does not have floating point calculation, I am trying to use pipes to bc to get the result, however I'm getting stumped on basic examples.
I tried to get the value of Pi to a scale, however,
even though the following works:
~ #bc -l
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
4/3
1.33333333333333333333
22/7
3.14285714285714285714
q
0
quitA non interactive version does not work:
#echo $(( 22/7 )) | bc
3This works:
#echo '22/7' | bc -l
3.14285714285714285714But I need to use variables. So it doesnt help that the following does not work:
#a=22 ; b=7
#echo $(( a/b )) | bc -l
3I'm obviously missing something in the syntax for using variables in Bash, and could use with some 'pointers' on what I've misunderstood.
As DigitalRoss said, I can use the following:
#echo $a / $b | bc -l
3.14285714285714285714However I cant use complex expressions like:
#echo $a / (( $b-34 )) | bc -l
-bash: syntax error near unexpected token `('
#echo $a / (( b-34 )) | bc -l
-bash: syntax error near unexpected token `('
#echo $a / (( b-34 )) | bc -l
-bash: syntax error near unexpected token `('Can someone give me a working correct syntax for getting floating point results with complicated arithmetic expresssions?
16 Answers
Just double-quote (") the expression:
echo "$a / ( $b - 34 )" | bc -lThen bash will expand the $ variables and ignore everything else and bc will see an expression with parentheses:
$ a=22
$ b=7
$ echo "$a / ( $b - 34 )"
22 / ( 7 - 34 )
$ echo "$a / ( $b - 34 )" | bc -l
-.81481481481481481481 6 Please note that your echo $(( 22/7 )) | bc -l actually makes bash calculate 22/7 and then send the result to bc. The integer output is therefore not the result of bc, but simply the input given to bc.
Try echo $(( 22/7 )) without piping it to bc, and you'll see.
scale variable determines number of digits after decimal separator
$ bc
$ scale=2
$ 3/4
$ .75 I would prefer awk over bc, it is does the same thing in one command and also gives you more flexibilty to add variables and format your output by using printf:
# Define vars in the command
awk -v a=3 -v b=2 'BEGIN{print a/b}'
1.5
# Define vars earlier and init with them awk vars
c=3
d=2
awk -v a=$c -v b=$d 'BEGIN{print a/b}'
1.5
# Use vars that are defined in script
a=3
b=2
awk 'BEGIN{print '$a'/'$b'}'
# Format your output using C printf syntax
awk -v a=3 -v b=2 'BEGIN{printf("%.3f\n", a/b)}'
1.500Also bc does not return a code error if it divides by zero, so you can't check the error:
echo 3/0 | bc -l
Runtime error (func=(main), adr=5): Divide by zero
# The error code is zero, that means there is no errors
echo $?
0While awk does return a code error 2:
awk -v a=3 -v b=0 'BEGIN{print a/b}'
awk: cmd. line:1: fatal: division by zero attempted
# awk returned code error 2, that indicates that something went wrong
echo $?
2The code error can be used to check for division by zero like:
# Set your own vars
if output=$(awk -v a=3 -v b=0 'BEGIN{print a/b}' 2> /dev/null); then echo "$output"
else echo "error, division by zero"
fi 1 u can handle the div-zero error checking directly at awk :
for a in 19 29 31; do for b in 11 3 0; do gawk -v PREC=512 -Mbe '$++NF= +(_=$NF) ? $(!!_)/_ : "div_by_zero"' \ \ CONVFMT='%.59g' OFS=' \t| ' <<< "${a} ${b}"; done; done
19 | 11 | 1.7272727272727272727272727272727272727272727272727272727273
19 | 3 | 6.3333333333333333333333333333333333333333333333333333333333
19 | 0 | div_by_zero
29 | 11 | 2.6363636363636363636363636363636363636363636363636363636364
29 | 3 | 9.6666666666666666666666666666666666666666666666666666666667
29 | 0 | div_by_zero
31 | 11 | 2.8181818181818181818181818181818181818181818181818181818182
31 | 3 | 10.333333333333333333333333333333333333333333333333333333333
31 | 0 | div_by_zeroif u don't need all that GMP precision, then mawk is willing to directly return an infinity instead of a fatal error message :
for a in 19 29 31; do for b in 11 3 0; do mawk '$++NF=$++_/$(_+_--)' CONVFMT='%.19g' OFS='\t' <<<"$a $b";done;done
19 11 1.727272727272727293
19 3 6.333333333333333037
19 0 inf
29 11 2.636363636363636243
29 3 9.666666666666666075
29 0 inf
31 11 2.818181818181818343
31 3 10.33333333333333393
31 0 infor better yet, do it from one single call to awk instead of calling it nonstop :
for a in 19 29 31; do for b in 11 3 0; do echo "${a} ${b}" done; done | mawk '$++NF = $(++_) / $(_+_--)' CONVFMT='%.19g' OFS='\t'
19 11 1.727272727272727293
19 3 6.333333333333333037
19 0 inf
29 11 2.636363636363636243
29 3 9.666666666666666075
29 0 inf
31 11 2.818181818181818343
31 3 10.33333333333333393
31 0 infOr if you so prefer, have mawk call gawk-gmp indirectly :
echo "22 7\n22 4\n22 0" | mawk '$++NF = substr(_=__="", (__="gawk -v PREC=65536 -Mbe"\ " \47BEGIN { printf(\"%.127f\","(+(_=$(NF-!_))\ ? "("($!__)")/("(_)")" : (+(_=$!__)<-_?__:"-") \ "log(_<_)")") } \47" ) | getline _, close(__))_'
22 7 3.1428571428571428571428571428571428571428571428571428………
22 4 5.5000000000000000000000000000000000000000000000000000………
22 0 +inf To expand on the Feb 22, '13 and Sept 1, '22 answers, using echo is not required for this, either. You can also use the here strings (<<<) to input your data into bc.
bc -l <<< "$a / ( $b - 34 )"This is useful for making quick one-liners, sometimes.
user@machine:~$ a=22; b=7; bc -l <<< "$a / ( $b - 34 )"
-.81481481481481481481scale in bc can be added to this very easily.
user@machine:~$ a=22; b=7; c=2; bc <<< "scale=$c; $a / ( $b - 34 )"
-.81The here string is from bash and not bc, so this works with many other terminal commands, as well.