bash
In bash
, that's probably as good as it gets. That uses a shell builtin. If you need the result in a variable, you could use command substitution, or the bash
specific (though now also supported by zsh
):
printf -v int %.0f "$float"
You could do:
float=1.23
int=${float%.*}
But that would remove the fractional part instead of giving you the nearest integer and that wouldn't work for values of $float
like 1.2e9
or .12
for instance.
Also note the possible limitations due to the internal representation of floats:
$ printf '%.0f\n' 1e50
100000000000000007629769841091887003294964970946560
You do get an integer, but chances are that you won't be able to use that integer anywhere.
Also, as noted by @BinaryZebra, in several printf
implementations (bash, ksh93, yash, not GNU, zsh, dash), it is affected by the locale (the decimal separator which can be .
or ,
).
So, if your floats are always expressed with the period as the decimal separator and you want it to be treated as such by printf
regardless of the locale of the user invoking your script, you'd need to fix the locale to C:
LC_ALL=C printf '%.0f' "$float"
With yash
, you can also do:
printf '%.0f' "$(($float))"
(see below).
POSIX
printf "%.0f\n" 1.1
is not POSIX as %f
is not required to be supported by POSIX.
POSIXly, you can do:
f2i() {
awk 'BEGIN{for (i=1; i<ARGC;i++)
printf "%.0f\n", ARGV[i]}' "$@"
}
That one is not affected by the locale (the comma cannot be a decimal separator in awk
since it's already a special character in the syntax there (print 1,2
, same as print 1, 2
to pass two arguments to print
)
zsh
In zsh
(which supports floating point arithmetic (decimal separator is always the period)), you have the rint()
math function to give you the nearest integer as a float (like in C
) and int()
to give you an integer from a float (like in awk
). So you can do:
$ zmodload zsh/mathfunc
$ i=$((int(rint(1.234e2))))
$ echo $i
123
Or:
$ integer i=$((rint(5.678e2)))
$ echo $i
568
However note that while double
s can represent very large numbers, integers are much more limited.
$ printf '%.0f\n' 1e123
999999999999999977709969731404129670057984297594921577392083322662491290889839886077866558841507631684757522070951350501376
$ echo $((int(1e123)))
-9223372036854775808
ksh93
ksh93 was the first Bourne-like shell to support floating point arithmetic. ksh93 optimises command substitution by not using a pipe or forking when the commands are only builtin commands. So
i=$(printf '%.0f' "$f")
doesn't fork. Or even better:
i=${ printf '%.0f' "$f"; }
which doesn't fork either but also doesn't go all the trouble of creating a fake subshell environment.
You can also do:
i=$((rint(f)))
But beware of:
$ echo "$((rint(1e18)))"
1000000000000000000
$ echo "$((rint(1e19)))"
1e+19
You could also do:
integer i=$((rint(f)))
But like for zsh
:
$ integer i=1e18
$ echo "$i"
1000000000000000000
$ integer i=1e19
$ echo "$i"
-9223372036854775808
Beware that ksh93
floating point arithmetic honour the decimal separator setting in the locale (even though ,
is otherwise a math operator ($((1,2))
would be 6/5 in a French/German... locale, and the same as $((1, 2))
, that is 2 in an English locale).
yash
yash also supports floating point arithmetic but doesn't have math functions like ksh93
/zsh
's rint()
. You can convert a number to integer though by using the binary or operator for instance (also works in zsh
but not in ksh93
). Note however that it truncates the decimal part, it doesn't give you the nearest integer:
$ echo "$((0.237e2 | 0))"
23
$ echo "$((1e19 | 0))"
-9223372036854775808
yash
honours the locale's decimal separator on output, but not for the floating point literal constants in its arithmetic expressions, which can cause surprises:
$ LC_ALL=fr_FR.UTF-8 ./yash -c 'a=$((1e-2)); echo $(($a + 1))'
./yash: arithmetic: `,' is not a valid number or operator
It's good in a way in that you can use floating point constants in your scripts that use the period and not have to worry that it will stop working in other locales, but still be able to deal with the numbers as expressed by the user as long as you remember to do:
var=$((10.3)) # and not var=10.3
... "$((a + 0.1))" # and not "$(($a + 0.1))".
printf '%.0f\n' "$((10.3))" # and not printf '%.0f\n' 10.3
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.
Best Answer
Use
printf
:Since
%e
might change the exponent, to be sure of keeping it the same, you can use the shell's string manipulation features to separate the number from the exponent and print each separately:The
${B%E*}
prints everything up to the 1stE
and${B##*E}
is everything after the firstE
.