9

i am trying to solve this bash script which reads an arithmetic expression from user and echoes it to the output screen with round up of 3 decimal places in the end.

sample input

5+50*3/20 + (19*2)/7

sample output

17.929

my code is

read x
echo "scale = 3; $x" | bc -l

when there is an input of

5+50*3/20 + (19*2)/7

**my output is **

17.928

which the machine wants it to be

17.929

and due to this i get the solution wrong. any idea ?

Mat
  • 202,337
  • 40
  • 393
  • 406
krrish
  • 353
  • 3
  • 15
  • Your question is quite ambiguous. What do you call "sample output" ? What do you call "my output" ? What do you call "machine wants it to be" ? Actually, what is the output that you want your script to generate: truncated or rounded ? –  Dec 07 '14 at 10:07
  • sample input is the input which the machine generates to check whether my script is right or not, for which it expects the output to be the output which is sample output. and my output is the output which my script generates, what i need is my output to be similar to sample output @YvesDaoust – krrish Dec 08 '14 at 11:34
  • Maybe I am not sure that it can be wrong to say that you didn't make the explanation less obscure. Truncated or rounded ? –  Dec 08 '14 at 11:39

3 Answers3

5

The key here is to be sure to use printf with the formatting spec of "%.3f" and printf will take care of doing the rounding as you wish, as long as "scale=4" for bc.

Here's a script that works:

echo -e "please enter math to calculate: \c"
read x
printf "%.3f\n" $(echo "scale=4;$x" | bc -l)

You can get an understanding of what is going on with the above solution, if you run this command at the commandline: echo "scale=4;5+50*3/20 + (19*2)/7" | bc the result will be 17.9285. When that result is provided to printf as an argument, the function takes into account the fourth decimal place and rounds up the value so that the formatted result displays with precisely three decimal places and with a value of 17.929.

Alternatively, this works, too without a pipe by redirecting the here document as input for bc, as follows which avoids creating a sub-shell:

echo -e "please enter math to calculate: \c"
read x
printf "%.3f\n" $(bc -l <<< "scale=4;$x")
slevy1
  • 3,797
  • 2
  • 27
  • 33
  • Might as well be consistent `printf "%.3f" $(printf "scale=4;5+50*3/20 + (19*2)/7\n" | bc)`. `:)` – David C. Rankin Dec 07 '14 at 08:37
  • One doesn't need the formatting capabilities of printf in both places. I intentionally use echo b/c there is nothing to format in that particular portion. On the other hand for the rounding printf "%.3f" makes sense. You may wish to see the accepted answer at stackoverflow.com/questions/2395284/… – – slevy1 Dec 07 '14 at 11:08
  • Please don't think I was saying mixing both was wrong. But you must think about the confusion for the other person while pondering ("Now why did she use `printf` here and `echo` there and not `printf` in both places?") Your explanation above is perfect. – David C. Rankin Dec 07 '14 at 17:31
  • 1
    If you do the rounding with printf, you can remove the nasty `scale`. – Cimbali Dec 08 '14 at 12:05
  • Thanks for sharing. By leaving out 'scale' a value of `17.92857142857142857142` results on a 64-bit box for printf() to then round and display. By using 'scale', printf() needs to round only `17.9285`. – slevy1 Dec 08 '14 at 21:20
3

You are not rounding the number, you are truncating it.

$ echo "5+50*3/20 + (19*2)/7" | bc -l
17.92857142857142857142
$ echo "scale = 3; 5+50*3/20 + (19*2)/7" | bc -l
17.928

The only way I know to round a number is using awk:

$ awk  'BEGIN { rounded = sprintf("%.3f", 5+50*3/20 + (19*2)/7); print rounded }'
17.929

So, in you example:

read x
awk  'BEGIN { rounded = sprintf("%.3f", $x; print rounded }'
jherran
  • 3,337
  • 8
  • 37
  • 54
2

I entirely agree with jherran that you are not rounding the number, you are truncating it. I would go on to say that scale is probably just not behaving at all the way you want it, possibly in a way that noone would want it to behave.

> x="5+50*3/20 + (19*2)/7"
> echo "$x" | bc -l
17.92857142857142857142
> echo "scale = 3; $x" | bc -l
17.928

Furthermore, because of the behaviour of scale, you are rounding each multiplication/division separately from the additions. Let me prove my point with some examples :

> echo "scale=0; 5/2" | bc -l
2
> echo "scale=0; 5/2 + 7/2" | bc -l
5
> echo "5/2 + 7/2" | bc -l
6.00000000000000000000

However scale without any operation doesn't work either. There is an ugly work-around :

> echo "scale=0; 5.5" | bc -l
5.5
> echo "scale=0; 5.5/1" | bc -l
5

So tow things come out of this.

  • If you want to use bc's scale, do it only for the final result already computed, and even then, beware.

  • Remember that rounding is the same as truncating a number + half of the desired precision.

Let us take the example of rounding to the nearest integer, if you add .5 to a number that should be rounded up, its integer part will take the next integer value and truncation will give the desired result. If that number should have been rounded down, then adding .5 will not change its integer value and truncation will yield the same result as when nothing was added.

Thus my solution follows :

> y=$(echo "$x" | bc -l)
> echo "scale=3; ($y+0.0005)/1" | bc -l # scale doesn't apply to the +, so we get the expected result
17.929

Again, note that the following doesn't work (as explained above), thus breaking it up in two operations is really needed :

> echo "scale=3; ($x+0.0005)/1" | bc -l
17.928
Community
  • 1
  • 1
Cimbali
  • 11,012
  • 1
  • 39
  • 68
  • actually it dint work, i have marked an answer below, it worked. anyways i am so greatful for taking your time and explaining, i understood the basic concept, thanks @Climbali – krrish Dec 08 '14 at 11:46