### How to round decimals using bc in bash?

A quick example of what I want using bash scripting:

`#!/bin/bash echo "Insert the price you want to calculate:" read float echo "This is the price without taxes:" echo "scale=2; $float/1.18" |bc -l read -p "Press any key to continue..." bash scriptname.sh`

Assuming that the price is: 48.86 The answer will be:41.406779661 (41.40 actually because I'm using

`scale=2;`

)**My Question is:**How I round the second decimal to show the answer in this way?: 41.41IMHO, _nobody has answered this question satisfactorily_, perhaps because `bc` cannot achieve what is being requested (or at least the question I was asking when I found this post), which is **how to round decimals using `bc`** (that happens to be called by `bash`).

I know this question is old, but I can't resist a comment pushing my own implementation of bc: https://github.com/gavinhoward/bc. For that, it is in the built-in library with the `r()` function, so you can use `bc -le "r($float/1.18, 2)"` .

user85321 Correct answer

8 years agoA bash round function:

`round() { echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc)) };`

Used in your code example:

`#!/bin/bash # the function "round()" was taken from # http://stempell.com/2009/08/rechnen-in-bash/ # the round function: round() { echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)" | bc)) }; echo "Insert the price you want to calculate:" read float echo "This is the price without taxes:" #echo "scale=2; $float/1.18" |bc -l echo $(round $float/1.18 2); read -p "Press any key to continue..."`

Good luck :o)

btw, if the number is negative, we have to use `-0.5`

Couldn't get the example to work on the testing console! "Debugging" a little revealed that for e. g. `$2 = 3` I had to use `echo $(env printf %.3f $(echo "scale=3;((1000*$1)+0.5)/1000" | bc))`. Mind the __env__ before printf! This'll teach ya again that it's always important to *understand* what you're copy-pasting from elsewhere.

@Aquarius Power thanks for the inspiration! I've now forked a version of the above script which will work with both negative and positive numbers.

This is unnecessarily complicated and does all kinds of extra arithmetic to arrive at the simpler answers provided by migas and zuberuber.

@AquariusPower +1 yes, it's different for negative numbers. To test for a negative (non-integer!) number, I tried a bit and settled for `if [ `echo "$1 / 1" | bc` -gt 0 ] ` - is there a more elegant way to check (except parsing the string for "-")?

`echo $(echo $(printf …))` is unnecessary, just do `round(){ printf … ;}` and call it with `round …`. If you want a following linebreak, change `%.$2f` to `%.$2f\\n`.

`printf %.$2f` already rounds and hence your round-construction redundant. Just take `round() { printf %.$2f $1; }`.

Simplest solution:

`printf %.2f $(echo "$float/1.18" | bc -l)`

As noted also in a comment above (http://askubuntu.com/a/179949/512213), this would behave incorrectly for negative numbers, unfortunately.

@RobertG: Why? This solution doesn't use `+0.5`. Try `printf "%.2f\n" "$(bc -l <<<"48.86/1.18")" "$(bc -l <<<"-48.86/1.18")"` and you will get `41.41` and `-41.41`.

Also possible using a pipe: `echo "$float/1.18" | bc -l | xargs printf %.2f`

The solution in this answer gives me: `bash: printf: 1.69491525423728813559: invalid number`, depending on locale.

Bash/awk rounding:

`echo "23.49" | awk '{printf("%d\n",$1 + 0.5)}'`

If you have python you can use something like this:

`echo "4.678923" | python -c "print round(float(raw_input()))"`

Thanks for the tip but it doesn't solve what I need. I have to do it just by using bash...

The python command is more readable and good for quick scripts. Also supports arbitrary digit rounding by adding e.g. ", 3" to the round function. Thanks

`echo "$float" |awk '{printf "%.2f", $1/1.18}'` will perform the question's requested math to the requested percision of hundredths. That's as much "using bash" as the `bc` call in the question.

For Python you can just use something like `python -c "print(round($num))"` where `num=4.678923`. There's no need to muck about with stdin. You can also round to n digits like so: `python -c "print(round($num, $n))"`.

I managed to do something pretty neat with that solution, my problem was 0.5, in this case I didn't always get the round I needed so I came up with the following: `echo 5 | python -c "print int(round((float(raw_input())/2)-0.5))"` (When + will round up and - round down).

it's quite funny to call `awk` or `python` to call `printf`, you'd better do just the same without them: ```bash echo "4.678923" | xargs printf "%.2f" ```

Here's a purely bc solution. Rounding rules: at +/- 0.5, round away from zero.

Put the scale you're looking for in $result_scale; your math should be where $MATH is located in the bc command list:

`bc <<MATH h=0 scale=0 /* the magnitude of the result scale */ t=(10 ^ $result_scale) /* work with an extra digit */ scale=$result_scale + 1 /* your math into var: m */ m=($MATH) /* rounding and output */ if (m < 0) h=-0.5 if (m > 0) h=0.5 a=(m * t + h) scale=$result_scale a / t MATH`

very interesting! `MATH=-0.34;result_scale=1;bc <

I nominate this for "best answer".

I know it's an old question, but I have a pure 'bc'-solution without 'if' or branches:

`#!/bin/sh bcr() { echo "scale=$2+1;t=$1;scale-=1;(t*10^scale+((t>0)-(t<0))/2)/10^scale" | bc -l }`

Use it like

`bcr '2/3' 5`

or`bcr '0.666666' 2`

--> (expression followed by scale)That's possible because in bc (like C/C++) it's allowed to mixin logical expressions in your calculations. The expression

`((t>0)-(t<0))/2)`

will evaluate to +/-0.5 depending on the sign of 't' and therefore use the right value for rounding.This looks neat, but `bcr "5 / 2" 0` returns `2` instead of `3`. Am I missing something?

Here's an abbreviated version of your script, fixed to provide the output you want:

`#!/bin/bash float=48.86 echo "You asked for $float; This is the price without taxes:" echo "scale=3; price=$float/1.18 +.005; scale=2; price/1 " | bc`

Note that rounding up to nearest integer is equivalent to adding .5 and taking the floor, or rounding down (for positive numbers).

Also, the scale factor is applied at the time of operation; so (these are

`bc`

commands, you can paste them into your terminal):`float=48.86; rate=1.18; scale=2; p2=float/rate scale=3; p3=float/rate scale=4; p4=float/rate print "Compare: ",p2, " v ", p3, " v ", p4 Compare: 41.40 v 41.406 v 41.4067 # however, scale does not affect an entered value (nor addition) scale=0 a=.005 9/10 0 9/10+a .005 # let's try rounding scale=2 p2+a 41.405 p3+a 41.411 (p2+a)/1 41.40 (p3+a)/1 41.41`

Pure bc implementation as requested

`define ceil(x) { auto os,xx;x=-x;os=scale;scale=0 xx=x/1;if(xx>x).=xx-- scale=os;return(-xx) }`

if you put that in a file called functions.bc then you can round up with

`echo 'ceil(3.1415)' | bc functions.bc`

Code for the bc implementation found on http://phodd.net/gnu-bc/code/funcs.bc

I'm still looking for a pure

`bc`

answer to how to round just one value within a function, but here's a pure`bash`

answer:`#!/bin/bash echo "Insert the price you want to calculate:" read float echo "This is the price without taxes:" embiggen() { local int precision fraction="" if [ "$1" != "${1#*.}" ]; then # there is a decimal point fraction="${1#*.}" # just the digits after the dot fi int="${1%.*}" # the float as a truncated integer precision="${#fraction}" # the number of fractional digits echo $(( 10**10 * $int$fraction / 10**$precision )) } # round down if negative if [ "$float" != "${float#-}" ] then round="-5000000000" else round="5000000000" fi # calculate rounded answer (sans decimal point) answer=$(( ( `embiggen $float` * 100 + $round ) / `embiggen 1.18` )) int=${answer%??} # the answer as a truncated integer echo $int.${answer#$int} # reassemble with correct precision read -p "Press any key to continue..."`

Basically, this carefully extracts the decimals, multiplies everything by 100 billion (10¹⁰,

`10**10`

in`bash`

), adjusts for precision and rounding, performs the actual division, divides back to the appropriate magnitude, and then reinserts the decimal.Step-by-step:

The

`embiggen()`

function assigns the truncated integer form of its argument to`$int`

and saves the numbers after the dot in`$fraction`

. The number of fractional digits is noted in`$precision`

. The math multiplies 10¹⁰ by the concatenation of`$int`

and`$fraction`

and then adjusts that to match the precision (e.g.`embiggen 48.86`

becomes 10¹⁰ × 4886 / 100 and returns`488600000000`

which is 488,600,000,000).We want a final precision of hundredths, so we multiply the first number by 100, add 5 for rounding purposes, and then divide the second number. This assignment of

`$answer`

leaves us at a hundred times the final answer.Now we need to add the decimal point. We assign a new

`$int`

value to`$answer`

excluding its final two digits, then`echo`

it with a dot and the`$answer`

excluding the`$int`

value that's already taken care of. (Never mind the syntax highlighting bug that makes this appear like a comment)(Bashism: exponentiation is not POSIX, so this is a bashism. A pure POSIX solution would require loops to add zeros rather than using powers of ten. Also, "embiggen" is a perfectly cromulant word.)

One of the main reasons I use

`zsh`

as my shell is that it supports floating point math. The solution to this question is quite straightforward in`zsh`

:`printf %.2f $((float/1.18))`

(I'd love to see somebody add a comment to this answer with the trick to enabling floating point arithmetic in

`bash`

, but I'm pretty sure that such a feature doesn't yet exist.)if you have the result, for instance consider 2.3747888

all you have to do is:

`d=$(echo "(2.3747888+0.5)/1" | bc); echo $d`

this rounds the number correctly example:

`(2.49999 + 0.5)/1 = 2.99999`

the decimals are removed by

`bc`

and so it rounds down to 2 as it should haveI had to calculate the total duration of a collection of audio files.

So I had to:

A. obtain the duration for each file (

*not shown*)B. add up all the durations (they were each in

`NNN.NNNNNN`

(fp) seconds )C. separate hours, minutes, seconds, subseconds.

D. output a string of HR:MIN:SEC:FRAMES, where frame = 1/75 sec.

*(Frames come from SMPTE code used in studios.)*A: use

`ffprobe`

and parse duration line into a fp number*(not shown)*B:

`# add them up as a series of strings separated by "+" and send it to bc arr=( "${total[@]}" ) # copy array # IFS is "Internal Field Separator" # the * in arr[*] means "all of arr separated by IFS" # must have been made for this IFS='+' sum=$(echo "scale=3; ${arr[*]} "| bc -l)# (-l= libmath for fp) echo $sum`

C:

`# subtract each amount of time from tt and store it tt=$sum # tt is a running var (fp) hrs=$(echo "$tt / 3600" | bc) tt=$(echo "$tt - ( $hrs * 3600 )" | bc ) min=$(echo "$tt / 60" | bc ) tt=$(echo "$tt - ($min *60)" | bc ) sec=$(echo "$tt/1" | bc ) tt=$(echo "$tt - $sec" | bc ) frames=$(echo "$tt * 75" | bc ) # 75 frames /sec frames=$(echo "$frames/1" | bc ) # truncate to whole #`

D:

`#convert to proper format with printf (bash builtin) hrs=$(printf "%02d\n" $hrs) # format 1 -> 01 min=$(printf "%02d\n" $min) sec=$(printf "%02d\n" $sec) frames=$(printf "%02d\n" $frames) timecode="$hrs:$min:$sec:$frames" # timecode "01:13:34:54"`

License under CC-BY-SA with attribution

Content dated before 6/26/2020 9:53 AM

Luis Alvarado 8 years ago

I find it weird because "printf "%0.2f\n" 41.445" does now work but "printf "%0.2f\n" 41.435 and printf "%0.2f\n" 41.455" do. Even your own case works (On 12.04) but not with the .445