Rounding Numbers with bc in Bash
Asked Answered
I

3

12

I want to compute an average with 3 decimal figures, rounded to nearest, using bc.

For example:

average of 3, 3 and 5 should yield 3.667

and

average of 3, 3 and 4 should yield 3.333

I tried:

echo "scale=3; $sum/$n+0.0005" | bc

but scale doesn't behave as I expect. What can I do to solve my problem?

Irenairene answered 11/11, 2014 at 9:4 Comment(15)
You're adding 0.0005, so bc gladly answers with 4 digits after decimal point.Aquarius
So what should i do ?Irenairene
How about you remove +0.0005?Aquarius
Why do you need that?Aquarius
because if number is 9.9999Irenairene
i should prinf 10.000Irenairene
bc dont round that numberIrenairene
So you're having an XYproblem.Aquarius
and i want to how can i do that, not just for a problemIrenairene
I hope you read the link I put in my previous comment. You should definitely edit your question and specify: 1. Clearly state what you want to achieve: I want to compute an average with 3 decimal figures, rounded to nearest. 2. State what you tried, and say why it doesn't work.Aquarius
sorry im not good at english and i cant express my porblem clearlyIrenairene
There are a load of answers to your question here: askubuntu.com/questions/179898/…Feinberg
thanks so much this will help a lotIrenairene
I completely rephrased you question, with some examples. Please check that the examples really correspond to what you want to achieve! And also, from this, learn how to ask a question!Aquarius
i will try to ask questions like this thanks againIrenairene
A
8

Your trick to add 0.0005 is not a bad idea. Though, it doesn't quite work that way. scale is used internally when bc performs some operations (like divisions).

In your case, it would be better to perform the division first, maybe using a large scale or the -l switch to bc1 (if your version supports it), then add 0.0005 and then set scale=3 and perform an operation involving scale internally to have the truncation performed.

Something like:

`a=$sum/$n+0.0005; scale=3; a/1`

Of course, you'll want to proceed differently whether sum is positive or negative. Fortunately, bc has some conditional operators.

`a=$sum/$n; if(a>0) a+=0.0005 else if (a<0) a-=0.0005; scale=3; a/1`

You'll then want to format this answer using printf.

Wrapped in a function round (where you can optionally select the number of decimal figures):

round() {
    # $1 is expression to round (should be a valid bc expression)
    # $2 is number of decimal figures (optional). Defaults to three if none given
    local df=${2:-3}
    printf '%.*f\n' "$df" "$(bc -l <<< "a=$1; if(a>0) a+=5/10^($df+1) else if (a<0) a-=5/10^($df+1); scale=$df; a/1")"
}

Try it:

gniourf$ round "(3+3+4)/3"
3.333
gniourf$ round "(3+3+5)/3"
3.667
gniourf$ round "-(3+3+5)/3"
-3.667
gniourf$ round 0
0.000
gniourf$ round 1/3 10
0.3333333333
gniourf$ round 0.0005
0.001
gniourf$ round 0.00049
0.000

1 with the -l switch, scale is set to 20, which should be plenty enough.

Aquarius answered 11/11, 2014 at 12:28 Comment(4)
Thank you! it works. But it seems i have to work a lot to write codes like thisIrenairene
I worked on C++ and it was easy because syntax wasnt importantIrenairene
As i understand syntax (space .. etc) very important in BashIrenairene
Yeah, Bash's syntax is completely crazy. Good luck!Aquarius
P
4

Next function round argument 'x' to 'd' digits:

define r(x, d) {
    auto r, s

    if(0 > x) {
        return -r(-x, d)
    }
    r = x + 0.5*10^-d
    s = scale
    scale = d
    r = r*10/10
    scale = s  
    return r
} 
Pampuch answered 2/1, 2018 at 8:53 Comment(0)
L
1

This solution is not that flexibile (it just converts float to int), but it can deal with negative numbers:

e=$( echo "scale=0; (${e}+0.5)/1" | bc -l )
if [[ "${e}" -lt 0 ]] ; then
    e=$(( e - 1 ))
fi
Limen answered 28/5, 2019 at 20:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.