58

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

3
  • 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 Aug 24, 2012 at 16:44
  • 9
    IMHO, 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).
    – Adam Katz
    May 21, 2015 at 22:51
  • 2
    I know this question is old, but I can't resist a comment pushing my own implementation of bc: 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)" . May 14, 2019 at 0:14

16 Answers 16

45

Simplest solution:

printf %.2f $(echo "$float/1.18" | bc -l)
4
  • 3
    As noted also in a comment above (askubuntu.com/a/179949/512213), this would behave incorrectly for negative numbers, unfortunately.
    – RobertG
    Mar 2, 2016 at 16:35
  • 2
    @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.
    – musiphil
    Aug 12, 2016 at 17:20
  • 4
    Also possible using a pipe: echo "$float/1.18" | bc -l | xargs printf %.2f
    – dessert
    Nov 4, 2017 at 10:05
  • 1
    The solution in this answer gives me: bash: printf: 1.69491525423728813559: invalid number, depending on locale. Sep 12, 2019 at 6:25
38

A 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)

8
  • 1
    btw, if the number is negative, we have to use -0.5 Jun 4, 2014 at 4:47
  • 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. Oct 23, 2014 at 23:04
  • @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. Oct 23, 2014 at 23:48
  • This is unnecessarily complicated and does all kinds of extra arithmetic to arrive at the simpler answers provided by migas and zuberuber.
    – Adam Katz
    May 21, 2015 at 22:46
  • @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 "-")?
    – RobertG
    Mar 2, 2016 at 16:32
22

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()))"
7
  • Thanks for the tip but it doesn't solve what I need. I have to do it just by using bash...
    – blackedx
    Aug 24, 2012 at 14:39
  • 1
    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
    – Elle
    Nov 13, 2013 at 2:07
  • 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.
    – Adam Katz
    May 21, 2015 at 22:41
  • 2
    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))".
    – Six
    Nov 25, 2015 at 20:32
  • 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).
    – Yaron
    Jun 13, 2016 at 9:39
8

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
3
  • 2
    very interesting! MATH=-0.34;result_scale=1;bc <<MATH, #ObjectiveAndClear:5 as explained here :) Mar 2, 2016 at 20:42
  • I nominate this for "best answer". Jun 18, 2019 at 7:03
  • A detailed explanation would be in order. You could start with what exactly is scale.
    – x-yuri
    Oct 9, 2020 at 10:43
5

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
3

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 have

1
  • Why people write so many stuff if there is obvious short solution... My eyes were bleeding until this.
    – midenok
    Feb 12 at 11:48
3

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

1
  • You have to use bc -l funcs.bc <<< 'ceil(3.1415)' (or your echo equivalent), without the -l, you'll get the result but also an error: Runtime error (func=(main), adr=3): Function e not defined. because the standard math library is not used.
    – emk2203
    Jan 8, 2021 at 22:13
1
#!/bin/bash
# - loosely based on the function "round()", taken from 
# http://stempell.com/2009/08/rechnen-in-bash/

# - inspired by user85321 @ askubuntu.com (original author)
#   and Aquarius Power

# the round function (alternate approach):

round2()
{
    v=$1
    vorig=$v
    # if negative, negate value ...
    (( $(bc <<<"$v < 0") == 1 )) && v=$(bc <<<"$v * -1")
    r=$(bc <<<"scale=$3;(((10^$3)*$v/$2)+0.5)/(10^$3)")

    # ... however, since value was only negated to get correct rounding, we 
    # have to add the minus sign again for the resulting value ...

    (( $(bc <<< "$vorig < 0") == 1 )) && r=$(bc <<< "$r * -1")
    env printf %.$3f $r
};

echo "Insert the price you want to calculate:"
read float
echo "This is the price without taxes:"
round2 $float 1.18 2
echo && read -p "Press any key to continue..."

It is actually simple: there is no need to explicitly add a hardcoded "-0.5" variant for negative numbers. Mathematically spoken, we'll just compute the absolute value of the argument and still add 0.5 as we normally would. But since we (unfortunately) have no built-in abs() function at our disposal (unless we code one), we will simply negate the argument if it's negative.

Besides, it proved very cumbersome to work with the quotient as a parameter (since for my solution, I must be able to access the dividend and divisor separately). This is why my script has an additional third parameter.

3
  • @muru about your recent edits: herestrings instead of echo .. |, shell arithmetic, what is the point of echo $()? This is easy to explain: your here strings will always require a writable /tmp directory! And my solution will also work on a read-only environment, e. g. an emergency root shell where / is not always writable by default. So there was a good reason why I coded it that way. Apr 17, 2015 at 14:48
  • Herestrings, I agree. But when is echo $() ever needed? That and indentation prompted my edits, the herestrings just happened.
    – muru
    Apr 17, 2015 at 14:54
  • @muru Well the most pointless case of echo $() that was overdue to get fixed was in fact the penultimate line, lol. Thanks for the heads-up. At least this one does look much better now, I can't deny. Anyways, I loved the logic in this. Lots of boolean stuff, less superfluous "if"s (which surely would blow up the code by 50 percent). Apr 17, 2015 at 14:58
1

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

1

I 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"
0

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.

1
  • This looks neat, but bcr "5 / 2" 0 returns 2 instead of 3. Am I missing something?
    – user41313
    Jan 3, 2018 at 8:25
0
bc() {
        while :
        do
                while IFS='$\n' read i
                do
                        /usr/bin/bc <<< "scale=2; $i" | sed 's/^\./0&/'
                done
        done
}
0

You can use awk, no pipes necessary:

$> PRICE=48.86
$> awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}"
41.41

Set the price beforehand with the environment variable PRICE=<val>.

  • Then call the awk - $PRICE will go into awk as an awk variable price.

  • It is then rounded up to the nearest 100th with +.005.

  • The printf formatting option %.2f limits the scale to two decimal places.

If you have to do this a lot this way is a lot more efficient than the selected answer:

time for a in {1..1000}; do echo $(round 48.86/1.18 2) > /dev/random; done
real    0m8.945s
user    0m6.067s
sys 0m4.277s

time for a in {1..1000}; do awk -vprice=$PRICE "BEGIN{printf(\"%.2f\n\",price/1.18+0.005)}">/dev/random; done
real    0m2.628s
user    0m1.462s
sys 0m1.275s
0

PRINTF NOT RELIABLE:

The printf method is very neat, easy to use & listed in lots of how to's.

However, if you have hard coded a decimal point, you should know that your code will probably fail if used one of the many countries that use a comma separator.

A surprising number of countries actually do use this & many of them in Europe. Probably as many as use a dot. According to Wikipedia > Decimal_separator the comma is noted as an ISO/IEC Directive ...

This is what happens if your computer has its number system set to use commas:

printf %.2f 573,5489   

573,55

but

printf %.2f 573.5489   

printf: 573.5489: invalid number

How do we get around this?

Using 'bc' is one way. On its own, simply with scale=n. It doesn't need to be piped as in some of the examples. Remember that scale only works on division and to divide your input sum by one. See the answer by @user525061

The other, especially if you have already code using the printf method, could be to change the locale of your shell session using 'export'.

Try:

export LC_NUMERIC=C

But then this negates the printf simplicity ....

0

An easy and accurate way to do this is via calculation. Rounding a number up or down (and either with zero or the integer as starting point) to a specified significance is doable with just some calculations and not too difficult code. I use calc for this, but it works as well with bc.

For example, to round number up to specified (multiple of) decimals(s) with integer as starting point:

1. The numerical value (number) that is to be rounded up is obtained.

2. The numerical value / significance (decimal(s)) to which [1] has to be rounded up to is obtained. 3. Of the numerical value [1] is obtained its integer. 4. Of the numerical value [1] are obtained the decimals. 5. A multiplication factor is generated; how many times does [2] fit into [4]. 6. In case the multiplication factor is not a whole number, the multiplication factor will be its integer + 1 7. The numerical value to round up to at [2] is multiplied by the multiplication factor of [5], resulting in a new (decimal) numerical value that is rounded up to [2] 8. And so we can add up: INTEGER + NEW_DECIMALS = ROUNDED_NUMBER

You can basically do this in any code. I use bash for it, but since I'm noob with code in general, you might create it different than I did. Or, if you want you can just rip it from here https://github.com/bveldhuyzen/rounding-module

[EDIT]

Here's a bc example:

  • rounding up with bc scale=10 (or 100, whatever you want)
  • rounds up to specified significance (any decimals)
  • takes integer as starting point
#Input number
FULL_NUMBER=$(<full_number_1.txt) 

#Significance 
ROUND_TO_THIS=$(<round_to_this.txt) 

echo "$FULL_NUMBER" | awk '{print int($0)}' > integer.txt 
INTEGER=$(<integer.txt) 
  
echo "scale=10; $FULL_NUMBER - $INTEGER" | bc > leftover_decimals.txt 
DECIMALS=$(<leftover_decimals.txt)

echo "scale=10; $DECIMALS / $ROUND_TO_THIS" | bc > MPF.txt 
MPF=$(<MPF.txt) 
  
echo "$MPF" | awk '{print int($0)}' > MPF_total.txt 
MPF_TOTAL=$(<MPF_total.txt) 

echo "scale=10; $MPF - $MPF_TOTAL" | bc > steps_leftover_decimals.txt 
STEPS_LEFTOVER_DECIMALS=$(<steps_leftover_decimals.txt) 

awk '$1>0 {$1=1} 1' steps_leftover_decimals.txt > steps_leftover_decimals_1.txt 
STEPS_LEFTOVER_DECIMALS_1=$(<steps_leftover_decimals_1.txt) 

if [[ $STEPS_LEFTOVER_DECIMALS_1 -eq 1 ]] 
then 
echo "scale=10; $MPF_TOTAL + 1" | bc > MPF_done.txt 
else 
head -1 MPF_total.txt > MPF_done.txt 
fi 
 
MULTIPLICATION_FACTOR_TOTAL_DONE=$(<MPF_done.txt) 
  
echo "scale=10; $MULTIPLICATION_FACTOR_TOTAL_DONE * $ROUND_TO_THIS" | bc > new_decimals.txt 
NEW_DECIMALS=$(<new_decimals.txt) 
  
echo "scale=10; $INTEGER + $NEW_DECIMALS" | bc > rounded_number.txt 


#Output / rounded number
ROUNDED_NUMBER=$(<rounded_number.txt)

You can of course make the code shorter by removing the intermediate text files (sorry I'm used working like this).

Hope it makes sense.

[EDIT2]

For rounding down number to (multiple of) specified decimals with integer of full number as starting point, if multiplication factor is not a whole number, just take integer of multiplication factor * round_to_this.

[EDIT3]

Or just use the build-in rounding module of the calc app..

0

Now isn't this ugly? Might be better to stick to python which is also available in pretty much all distros

a=23.4353
python -c "print(round($a, 2))"
# 23.44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .