Velvet Star Monitor

Standout celebrity highlights with iconic style.

general

Floating point results in Bash integer division

Writer 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
quit

A non interactive version does not work:

#echo $(( 22/7 )) | bc
3

This works:

#echo '22/7' | bc -l
3.14285714285714285714

But I need to use variables. So it doesnt help that the following does not work:

#a=22 ; b=7
#echo $(( a/b )) | bc -l
3

I'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.14285714285714285714

However 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?

1

6 Answers

Just double-quote (") the expression:

echo "$a / ( $b - 34 )" | bc -l

Then 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.500

Also 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 $?
0

While 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 $?
2

The 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_zero

if 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 inf

or 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 inf

Or 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 )"
-.81481481481481481481

scale in bc can be added to this very easily.

user@machine:~$ a=22; b=7; c=2; bc <<< "scale=$c; $a / ( $b - 34 )"
-.81

The here string is from bash and not bc, so this works with many other terminal commands, as well.

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.