bc arithmetic Error
Asked Answered
G

3

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 ?

Guienne answered 7/12, 2014 at 7:32 Comment(3)
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 ?Asteriated
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 @YvesDaoustGuienne
Maybe I am not sure that it can be wrong to say that you didn't make the explanation less obscure. Truncated or rounded ?Asteriated
D
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")
Dauntless answered 7/12, 2014 at 8:29 Comment(5)
Might as well be consistent printf "%.3f" $(printf "scale=4;5+50*3/20 + (19*2)/7\n" | bc). :)Indulgent
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/… –Dauntless
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.Indulgent
If you do the rounding with printf, you can remove the nasty scale.Upgrade
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.Dauntless
N
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 }'
Nightie answered 7/12, 2014 at 7:42 Comment(0)
U
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
Upgrade answered 7/12, 2014 at 15:21 Comment(1)
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 @ClimbaliGuienne

© 2022 - 2024 — McMap. All rights reserved.