Floating point results in Bash integer division
Asked Answered
Y

6

66

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?

Yaron answered 22/2, 2013 at 2:21 Comment(1)
It is possible to teach bash e.g. integer division with floating point results: See: https://mcmap.net/q/93997/-division-in-script-and-floating-pointFroth
L
63

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
Liken answered 22/2, 2013 at 2:35 Comment(6)
Awesome. I never thought of using double quotes to expand the variables. Thank you!Yaron
Mhhh ? double quotes don't expand the variables but permit that.Slowworm
sweet, I would have switched to python if I had stumble upon this.Trimester
It seems I need -l for division, but not for addition. Should I simply always use -l?Murielmurielle
@user2023370: -l uses "mathlib" and also causes the scale to be set to 20 so results will be shown up to 20 decimal places. Without -l the default scale is 0. See man bcLiken
@AdrianPronk Yes, I recently found bc's scale parameter to help me here. But even thinking about precision also leads to using -l, as you will get consistent formatting ... and division works!Murielmurielle
D
13

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.

Defamation answered 7/4, 2015 at 23:36 Comment(0)
C
6

scale variable determines number of digits after decimal separator

$ bc
$ scale=2
$ 3/4
$ .75
Coordination answered 1/9, 2022 at 16:6 Comment(0)
S
3

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
Seidule answered 2/9, 2022 at 4:12 Comment(1)
I agree that awk is the best built-in math interpreter in bash. bash itself cannot do any floating point echo $((3/2)). bc cannot handle decimal exponent echo "2^1.5" | bc -l. But awk can do all of those!Publicness
C
0

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
Cyclosis answered 2/9, 2022 at 5:41 Comment(0)
S
0

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.

Samphire answered 15/9, 2023 at 1:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.