How to round floating point numbers in shell?

72,730

Rounding floating point numbers

What does "rounding a floating point number" mean?
That's easy, obviously... Where's my math book from school...

No, we already know nothing related to floating point numbers is easy:

For a start, there are multiple rounding modes:

Rounding upwards?
Rounding downwards?
Rounding to zero?
Rounding to nearest - ties to even?
Rounding to nearest - ties away from zero?

How to handle the corner cases? How to find out which are the corner cases?

OK, looks like we better use an implementation of the IEEE 754 standard, and let our system take care of that.

To round a floating point number in the shell, based on standard floating point arithmetic, we need three steps:

  • Convert the input text from a command line argument to a standard floating point number.
  • Round the floating point number using the normal IEEE 754 implementation.
  • Format the number as a string for output.

Turns out that the shell command printf can do all of this. It can be used to print numbers according to a format specification as described in man 3 printf. The numbers are rounded implicitly in the standard way if it is required for the output format:

The command

Round x to p digits precision with input as command line arguments:

printf "%.*f\n" "$p" "$x"

Or in a shell pipeline, with input of x on standard input, and p as argument:

echo "$x" | xargs printf "%.*f\n" "$p"

Examples:

$ printf '%.*f\n' 0 6.66
7
$ printf '%.*f\n' 1 6.66
6.7
$ printf '%.*f\n' 2 6.66
6.66
$ printf '%.*f\n' 3 6.66
6.660
$ printf '%.*f\n' 3 6.666
6.666
$ printf '%.*f\n' 3 6.6666
6.667

Bad traps

Beware the locale! It specifies the separator between the integral and fraction part - the ., as you may expect.
But see yourself what happens in a German locale, for example:

$ LC_ALL=de_DE.UTF-8 printf '%.*f\n' 3 6.6666
6,667

Yes, that's right 6,667 - six comma six six seven. That would mess up your script for sure.
(But only for the two customers in Germany. Except for the developer's machines currently debugging for these customers.)

More robust

To make it more robust, use:

LC_ALL=C /usr/bin/printf "%.*f\n" "$p" "$x"

or

echo "$x" | LC_ALL=C xargs /usr/bin/printf "%.*f\n" "$p"

This also uses /usr/bin/printf instead of the shell builtin of bash or zsh to work around minor inconsistencies in implementation of the printf variants, and prevent a very dirty effect when, in a German locale, LC_ALL is set, but not exported. Then, the builtin uses ,, and /usr/bin/printf uses ....


See also %g for rounding to a specified number of significant digits.

Share:
72,730

Related videos on Youtube

Volker Siegel
Author by

Volker Siegel

I like to answer older questions, if I have an additional perspective to the question to give. Adding an additional answer even if there are valid answers can still add value to the collection of answers and questions we are building here. (A late answer does, by it's nature, get not much attention, so it leads to exceptionally low reputation per answer. But hey, that's life, right?) And I feel it's the important thing here: We're answering professional questions in a professional way, and often do that quickly. That's of great value for the general public. But the real thing of value, that is of value hard to describe in simple terms, is the body of text, the whole collection that we are creating here together. All participants here, whether he or she cares more about asking, answering or collecting questions and answers. This applies to all StackExchange sites and topics in the same way. In some sites and topics, I like to add questions that have only the purpose of growing the collection, often more academic than practical, and of general interest while not too trivial. I'm here because I want to take part in the creation of this exceptional body of well structured knowledge.

Updated on September 18, 2022

Comments

  • Volker Siegel
    Volker Siegel almost 2 years

    How do I correctly round IEEE 754 floating point numbers on the command line?

    I want to specify the precision of the output number - the count of fractional digits.

    Rounding 6.66 to precision 1 should give 6.7, for example. More in the table below:

    Value   Precision  Rounded
    6.66    0          7
    6.66    1          6.7
    6.66    2          6.66
    6.66    3          6.660
    6.666   3          6.666
    6.6666  3          6.667
    

    It should be usable in an interactive shell, but ideally robust enough for using it in production shell scripts.