Search code examples
linuxbashbc

Rounding Numbers with bc in Bash


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?


Solution

  • 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.