1. Overview
We know that Bash cannot perform floating-point arithmetic natively. However, sometimes, we want to round the result when we do some division calculations. Of course, there are different rounding requirements.
In this tutorial, we’ll learn how to perform various rounding methods in Bash.
2. Introduction to the Problem
There are three standard rounding methods. Given a real number x:
- Floor rounding – The output is the greatest integer less than or equal to x
- Ceiling rounding – The result will be the least integer greater than or equal to x
- Half-up rounding – The half-way values of x are always rounded up
A few examples may help us understand them quickly:
Floor Rounding | Ceiling Rounding | Half Rounding Up | |
4 / 2 = 2 | 2 | 2 | 2 |
3 / 2 = 1.5 | 1 | 2 | 2 |
10 / 3 = 3.333… | 3 | 4 | 3 |
In this tutorial, we’ll focus on the divisions of positive integers.
Next, let’s see how to implement each rounding method in Bash with examples.
3. Floor Rounding
Doing floor rounding is easy in Bash. This is because Bash does floor rounding by division automatically.
Next, let’s see some examples with Bash’s arithmetic expressions:
$ echo "floor rounding: 4/2 = $(( 4 / 2 ))"
floor rounding: 4/2 = 2
$ echo "floor rounding: 3/2 = $(( 3 / 2 ))"
floor rounding: 3/2 = 1
$ echo "floor rounding: 10/3 = $(( 10 / 3 ))"
floor rounding: 10/3 = 3
As we can see, we don’t need to do any extra work here.
4. Ceiling Rounding
Unlike floor rounding, we need to implement ceiling rounding on our own. In this section, we’ll address two ways to do that.
4.1. Using the Remainder
Let’s say we want to apply ceiling rounding on X / Y. One idea is to check the remainder of X / Y: R = X % Y.
- R = 0: X is divisible by Y, so X / Y is the result
- R > 0: The result will be X / Y + 1
Further, Bash’s arithmetic expression evaluates True as 1 and False as 0:
$ echo $(( 1 > 0 ))
1
$ echo $(( -1 > 0 ))
0
Therefore, we can build an expression pattern to do ceiling rounding on divisions:
$(( ( X / Y ) + ( X % Y > 0 ) ))
Next, let’s do some tests:
$ echo "ceiling rounding: 4/2 = $(( ( 4 / 2 ) + ( 4 % 2 > 0 ) ))"
ceiling rounding: 4/2 = 2
$ echo "ceiling rounding: 3/2 = $(( ( 3 / 2 ) + ( 3 % 2 > 0 ) ))"
ceiling rounding: 3/2 = 2
$ echo "ceiling rounding: 10/3 = $(( ( 10 / 3 ) + ( 10 % 3 > 0 ) ))"
ceiling rounding: 10/3 = 4
As the tests above show, we’ve got the expected result.
4.2. Using a Math Trick
We’ve learned how to apply ceiling rounding on division by checking the remainder.
Alternatively, we can do it with a little math trick: Ceiling( X / Y ) = ( X + Y – 1 ) / Y
Now, let’s first test it with our examples to see if it works:
$ echo "ceiling rounding: 4/2 = $(( ( 4 + 2 - 1 ) / 2 ))"
ceiling rounding: 4/2 = 2
$ echo "ceiling rounding: 3/2 = $(( ( 3 + 2 - 1 ) / 2 ))"
ceiling rounding: 3/2 = 2
$ echo "ceiling rounding: 10/3 = $(( ( 10 + 3 - 1 ) / 3 ))"
ceiling rounding: 10/3 = 4
Good, we’ve got the correct results. Next, let’s understand why it works.
For the division X / Y, we have two cases:
- X is evenly divisible by Y: X = k * Y (k is an integer)
- X is not evenly divisible by Y: X = k * Y + r (k and r are both integers, and 0< r < Y)
Let’s look at the first case:
- We can split ( X + Y – 1 ) / Y into k * Y / Y + ( Y – 1 ) / Y.
- We’ve learned Bash always applies floor rounding to division.
- Therefore, we have (Y – 1) / Y = 0.
- Further, we have k * Y / Y + ( Y – 1 ) / Y = k * Y / Y = k. The k is exactly the result we want to get.
Next, let’s have a look at the second case:
- As we know X = k * Y + r, we can write ( X + Y – 1 ) / Y as ( k * Y + r + Y – 1 ) / Y.
- Similarly, we can split it into k * Y / Y + ( r + Y – 1 ) / Y.
The first part is straightforward: k * Y / Y = k. Now let’s take a closer look at the second part:
- We’ve known r is an interger and 0 < r < Y, so we have Y =< ( r + Y – 1 ) < 2Y
- Hence, we get ( r + Y – 1 ) / Y = 1.
- Finally, when X is not dividable by Y, ( X + Y – 1 ) / Y = k + 1. It’s the expected result after ceiling rounding.
As we can see, both cases give the correct result.
5. Half-up Rounding
5.1. A Similar Math Trick
We’ve learned to do ceiling rounding using math. Similarly, we can take the same idea for half-up rounding.
Since we would like to do half-up rounding, we add Y / 2 instead of Y – 1 to X.
So, we have the formula: Half-up( X / Y ) = ( X + Y / 2 ) / Y
Next, let’s do some tests:
$ echo "half-up rounding: 4/2 = $(( ( 4 + 2 / 2 ) / 2 ))"
half-up rounding: 4/2 = 2
$ echo "half-up rounding: 3/2 = $(( ( 3 + 2 / 2 ) / 2 ))"
half-up rounding: 3/2 = 2
$ echo "half-up rounding: 10/3 = $(( ( 10 + 3 / 2 ) / 3 ))"
half-up rounding: 10/3 = 3
As the output above shows, we’ve got the expected results.
5.2. Why Not Use printf “%.0f”?
We’ve learned the formula to do half-up rounding in a previous section.
However, we may see code to use the printf command or printf function to do half-up rounding in practice.
Let’s see a couple of examples:
$ printf "half-up rounding: 3/2 = %.0f\n" $( echo "scale=2; 3 / 2" | bc)
half-up rounding: 3/2 = 2
$ awk 'BEGIN { printf "half-up rounding: 3/2 = %.0f\n", 3 / 2 }'
half-up rounding: 3/2 = 2
The first example uses the bc command to calculate 3 / 2 and lets the printf command do the format printing.
The printf command follows C’s printf function’s specification. The format string “%.0f” sets the float precision to 0. That is, it only keeps the integer part. Also, printf will round to the nearest integer.
The second awk example doesn’t execute the printf command. Instead, awk uses its own printf statement to do the format printing.
awk‘s printf statement follows the same rule of C’s printf function. Therefore, it’s pretty much the same as the first example.
As the output above shows, both examples gave the correct answer when we calculated 3 / 2.
However, a corner case will break both commands:
$ printf "half-up rounding: 1/2 = %.0f\n" $( echo "scale=2; 1 / 2" | bc)
half-up rounding: 1/2 = 0
$ awk 'BEGIN { printf "half-up rounding: 1/2 = %.0f\n", 1 / 2 }'
half-up rounding: 1/2 = 0
As the test above shows, this time, we calculate 1/2. We know the result is 0.5. So, after the half-up rounding, we’re expecting to get 1. However, both commands gave 0.
This is because printf with format “%.0f” will round 0.5 to 0 instead of 1:
$ printf "%.0f\n" 0.5
0
Finally, let’s test the same calculation with our math approach:
$ echo "half-up rounding: 1/2 = $(( ( 1 + 2 / 2 ) / 2 ))"
half-up rounding: 1/2 = 1
As the output above shows, the ( X + Y / 2 ) / Y approach gives the correct result.
6. Conclusion
In this article, we’ve addressed applying different rounding methods to division calculation in Bash. Let’s summarize them in a table:
Rounding Method | X / Y |
Floor rounding | X / Y |
Ceiling rounding | (X + Y – 1) / Y or ( X / Y ) + ( X % Y > 0 ) |
Half-up rounding | (X + Y / 2) / Y |
Further, we’ve learned the printf approach is not a stable solution for half-up rounding.