Chapter 7: Working with Numbers - Floating-point numbers
Index
In Bash, working with floating-point numbers can be a bit tricky because Bash’s built-in arithmetic only supports integer operations. This means that directly performing calculations involving decimals or fractions is not possible using the default arithmetic capabilities of Bash ($((...))
or let
), which are limited to whole numbers.
To handle floating-point numbers, you need to use external tools like bc
(an arbitrary precision calculator) or printf
(a format string utility). These tools extend Bash’s capability to handle real numbers and precision-based arithmetic.
In the next sections we will learn about the commands bc
and printf
.
printf
command
An unconventional way, although practical, to work with floating point numbers with native bash is by using together with Scientific Notation.
What is the “Scientific Notation”? It’s a way to express numbers which has the following format.
<base>e<exponent>
which would be equivalent to <base> * 10^{<exponent>}
[1]
This way of expressing numbers will tell the “printf
” command where to put the decimal comma.
Let’s see how it works with the following script.
1 #!/usr/bin/env bash
2 ##Script: printf.sh
3 printf "Number 1: %.3f\n" 12345e3
4 printf "Number 2: %.3f\n" 12345e-3
5 printf "Number 3: %.3f\n" 1234.5e3
6 printf "Number 4: %.3f\n" 1234.5e-3
When you run the previous script you will get the following output in your terminal.
$ ./printf.sh
Number 1: 12345000.000
Number 2: 12.345
Number 3: 1234500.000
Number 4: 1.235
From this script and its output we can understand that the scientific notation is a way of representing numbers that tells how many places should the decimal comma be moved and in what direction.
In lines 1 and 3 of the output, the decimal comma will be moved to the right, while the lines 2 and 4 will move the decimal comma to the left.
Let’s summarize the basics of using this approach:
- Make sure the operation you want to do is expressed using integer numbers only (otherwise Bash will fail)
- Decide in advance how many decimals you want to have in your result.
- When you use the Bash arithmetic expansion make sure you multiply the operation by “
10**<#decimals>
”. Like that you will calculate a number that is “#decimals” times bigger. - Use scientific notation “
e-<#decimals>
” to be able to move the comma to the left to be able to have the number of decimals you want.
We could summarize it even more with the following line.
printf %.<precision>f "$((10**<multiplier>*<operation>))e-<multiplier>”
Now let’s use the previous points in a more complex operation. We are going to create a script that will calculate the volume of a sphere.
1 #!/usr/bin/env bash
2 #Script: sphere.sh
3 #!/usr/bin/env bash
4 PI=314
5 NUM_DECIMALS=2
6 RADIUS=2
7 printf "The volume of a sphere with radius $RADIUS is %.3f\n" "$((10**$NUM_DECIMALS * $PI * $RADIUS**3 * 4 / 3))e-$(($NUM_DECIMALS*2))"
This script will calculate the volume of a sphere with radius of 2 units. When you run the previous script you will see this in your terminal.
$ ./sphere.sh
The volume of a sphere with radius 2 is 33.493
Now that we know how to use the printf
command to operate with floating-point numbers, we are going to take a look to the command bc
.
bc
command
The “bc
” command is a tool that will allow us to develop more complex calculations. It can be used in both interactive and non-interactive ways. It comes with its own language (with similarities to the C programming language) that allows you to develop scripts making mathematical calculations.
To have a quick introduction to the language behind the “bc
” command, we are going to present a few tables with the basics so that we can move forward quickly.
The “bc
” command has its own syntax (almost a full programming language) that allows you to define complex programs.
Next we will present to you a few tables that contain a summary of the basic syntax we can use with the “bc
” command.
The first table contains the operands that you can use in the language of “bc
”.
Operand | Description |
---|---|
-expr |
Negation of a expression |
++var |
Pre-incrementing the variable before using the new value |
--var |
Pre-decrementing the variable before using the new value |
var++ |
Post-incrementing the variable after using the old value |
var-- |
Post-decrementing the variable after using the old value |
expr1 + expr2 |
Addition of the two expressions |
expr1 - expr2 |
Difference of the two expressions |
expr1 * expr2 |
Multiplication of the two expressions |
expr1 / expr2 |
Division of the two expressions |
expr1 % expr2 |
Reminder |
expr1 ^ expr2 |
Exponentiation. The second expression must be an integer |
( expr ) |
Force evaluation of the expression inside the parenthesis, altering the standard precedence |
var = expr |
Assigns the value of the expression to the variable |
var <op>= expr |
Equivalent to “var = var <op> expr ”, where “<op> ” is any of the previous operands (+ , - , * , …) |
The next table contains relational expression that you can use with the “bc
” command.
Relational Expression | Description |
---|---|
expr1 < expr2 |
Result is 1 (true) if “expr1 ” is strictly less than “expr2 ” |
expr1 <= expr2 |
Result is 1 (true) if “expr1 ” is strictly less than or equal to “expr2 ” |
expr1 > expr2 |
Result is 1 (true) if “expr1 ” is strictly greater than “expr2 ” |
expr1 >= expr2 |
Result is 1 (true) if “expr1 ” is strictly greater than or equal to “expr2 ” |
expr1 == expr2 |
Result is 1 (true) if “expr1 ” is equal to “expr2 ” |
expr1 != expr2 |
Result is 1 (true) if “expr1 ” is not equal to “expr2 ” |
The following table contains the boolean expressions that you can use with the “bc
” command.
Boolean Expression | Description |
---|---|
!expr |
Result is 1 (true) if “expr ” is 0 (zero, false). |
expr1 && expr2 |
Result is 1 (true) if both expressions are non-zero (true) |
expr1 || expr2 |
Result is 1 (true) if either expression in non-zero (true) |
The “bc
” command also contains some standard functions that are represented in the following table.
Standard Function | Desription |
---|---|
length(expr) |
Returns the number of significant digits in the expression |
read() |
Reads a number from the standard input |
scale(expr) |
Number of digits after the decimal point in the expression |
sqrt(expr) |
Square root of the expression |
But the “bc
” command also comes with internal variables that you can use in your “bc scripts” which are defined in the following table.
Internal Variable | Description |
---|---|
scale |
Defines the number of digits after the decimal point. The default is zero |
ibase |
Defines the conversion base for input numbers. Default is base 10 |
obase |
Defines the conversion base for output numbers. Default is base 10 |
last |
It contains the value of the last printed number |
The next table contains the statements you can use in your “bc scripts”.
Statement | Description |
---|---|
“string” |
The string is printed to the output |
print list |
Provides another method to print information. “list ” is a list of expressions and strings separated by commas. |
{statement_list} |
Allows multiple statements to be grouped together for execution |
if (expression){ |
If “expression ” is non-zero, “statements1 ” is executed.If “ expression ” is zero and “else ” is present, “statements2 ” will be executed. |
while (expression) { |
Will execute “statements ” as long as “expression ” is non-zero (true) |
for([expr1];[expr2];[expr3]){ |
“expr1 ” is evaluated once before “statements ” are executed. “ expr2 ” is evaluated before each execution of “statements ”. If it’s evaluated to non-zero, “statements ” will be executed. If it’s evaluated to zero, the execution of the loop will be terminated.“ expr3 ” is evaluated after the execution of “statements ” and before “expr2” is evaluated. |
break |
Forces to exit in the most recent enclosing “while ” or “for ” loop statement. |
continue |
Forces the execution of the next iteration in the most recent enclosing “while ” or “for ” loop statement. |
halt |
Causes the “bc ” processor to quit when it’s executed |
return |
Returns value zero from a function |
return (expression) |
Returns the value of “expression ” from a function. |
The “bc
” command comes as well with what are called “Pseudo Statements” which are the following ones:
limits
: Prints the limits enforced by the version of “bc
”quit
: Causes the “bc
” processor to quit, regardless of where the quit statement is found.- “
if (0 == 1) quit
” will cause “bc
” to terminate. - “
if (0 == 1) halt
” will NOT cause “bc
” to terminate.
- “
warranty
: Prints a warranty notice
You can also define functions in a “bc script”. For that you need to use the following syntax.
define name (params) {
auto_list
statement_list
}
Where:
name
is the name of your functionparams
is a list of zero or more parameters separated by commasauto_list
is an optional list of variables intended to be used locally to the function. The syntax is “auto variable_name,...
”statement_list
is a list of statements that you will use in the function
The “bc
” command comes as well with some mathematical functions that you can use when you invoke the “bc
” command with the “-l
” flag. The functions are as follows:
s(x)
: Sine of “x
”. “x
” is in radiansc(x)
: Cosine of “x
”. “x
” is in radiansa(x)
: Arctangent of “x
”. It returns radiansl(x)
: Natural logarithm of “x
”e(x)
: Exponential function. It doese^{x}
As you can see, the “bc
” command comes with a whole language that deals with variables that are either numbers or an array of numbers. With this we are going to revisit the example of the script to calculate the volume of a sphere.
First of all we need to write a “bc
” script that will do the calculation.
1 /* Number of decimals */
2 scale=10
3 /* Arc whose tangent is 1 (a(1)) is 45 degrees, which is pi/4 */
4 pi=4*a(1)
5 /* Function that will calculate the volume of a sphere according to the radius */
6 define volume(radius) {
7 return((radius^3)*pi*4/3)
8 }
9 /* Call to calculate the volume of a sphere of radius 2 */
10 volume(2)
11 /* End of script */
12 halt
With this script we have to invoke the “bc
” command with the options “-l
” (to load the mathematical functions), “-q
” (quiet mode) and the name of the file holding the script previously written.
When execute it you will get the following output in your terminal.
$ bc -l -q volume_sphere_bc.txt
33.5103216341
The “problem” you could see with this script is that it’s static and calculates the volume of a sphere with radius 2. We could fix this very easily by replacing “2
” with “read()
”. In this case our new script would be as follows.
1 /* Number of decimals */
2 scale=10
3 /* Arc whose tangent is 1 (a(1)) is 45 degrees, which is pi/4 */
4 pi=4*a(1)
5 /* Function that will calculate the volume of a sphere according to the radius */
6 define volume(radius) {
7 return((radius^3)*pi*4/3)
8 }
9 /* Call to calculate the volume of a sphere of radius 2 */
10 volume(read()) /* <<<<<<<<<<<<<<<<< */
11 /* End of script */
12 halt
When you execute with the new script you will notice that the “bc
” command seems to be hanging.
$ bc -l -q volume_sphere_bc_read.txt
<waiting...>
This is because the script has stopped at “read()
” and it’s waiting for a number to be provided via the standard input. If we introduce “2
” and press enter we do have the following in our terminal.
$ bc -l -q volume_sphere_bc_read.txt
2
33.5103216341
We could make this script non-interactive by providing the number directly to the standard input when executing the “bc
” command. We can do that by piping[2] the number into the standard input, like this.
$ echo 2 | bc -l -q volume_sphere_bc_read.txt
33.5103216341
Summary
In this chapter we learnt how to deal with floating-point numbers in Bash by using the command “printf
” and the command “bc
” which comes with a very basic programming language and allows you to create very powerful scripts.
References
- https://www.ibm.com/docs/zh/aix/7.2?topic=b-bc-command
- https://en.wikipedia.org/wiki/Bc_(programming_language)
1. Will fix the mathematical representation at some point.↩
2. We will learn more about pipes in a later chapter.↩