Arredondando números de ponto flutuante
O que significa "arredondar um número de ponto flutuante" significa?
Isso é fácil, obviamente ... Onde está o meu livro de matemática da escola ...
Não, já sabemos que nada relacionado a números de ponto flutuante é fácil:
Para começar, existem vários modos de arredondamento:
Rounding upwards?
Rounding downwards?
Rounding to zero?
Rounding to nearest - ties to even?
Rounding to nearest - ties away from zero?
Como lidar com os casos de canto? Como descobrir quais são os casos difíceis?
OK, parece que é melhor usarmos uma implementação do padrão IEEE 754 e deixar que o nosso sistema cuide disso.
Para arredondar um número de ponto flutuante no shell, com base na aritmética de ponto flutuante padrão, precisamos de três etapas:
- Converta o texto de entrada de um argumento de linha de comando em um número de ponto flutuante padrão.
- Arredonda o número de ponto flutuante usando a implementação normal do IEEE 754.
- Formate o número como uma string para saída.
Acontece que o comando shell printf
pode fazer todas isto. Ele pode ser usado para imprimir números de acordo com uma especificação de formato, conforme descrito em man 3 printf
. Os números são arredondados implicitamente na forma padrão, se for necessário para o formato de saída:
O comando
Redonda a precisão de x
a p
dígitos com entrada como argumentos de linha de comando:
printf "%.*f\n" $p $x
Ou em um pipeline de shell, com entrada de x
na entrada padrão e p
como argumento:
echo $x | xargs printf "%.*f\n" $p
Exemplos:
$ 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
armadilhas ruins
Cuidado com a localidade! Ele especifica o separador entre as partes integral e fracionária - o .
, como você pode esperar.
Mas veja a si mesmo o que acontece em um local alemão, por exemplo:
$ LC_ALL=de_DE.UTF-8 printf '%.*f\n' 3 6.6666
6,667
Sim, está certo 6,667
- seis vírgula seis seis sete. Isso iria atrapalhar seu roteiro com certeza.
(Mas apenas para os dois clientes na Alemanha. Exceto pelas máquinas do desenvolvedor que atualmente estão depurando esses clientes.)
Mais robusto
Para torná-lo mais robusto, use:
LC_ALL=C /usr/bin/printf "%.*f\n" $p $x
ou
echo $x | LC_ALL=C xargs /usr/bin/printf "%.*f\n" $p
Isso também usa /usr/bin/printf
em vez do shell embutido de bash
ou zsh
para contornar pequenas inconsistências na implementação das variantes printf
e evitar um efeito muito sujo quando, em uma localidade alemã, LC_ALL
está definido, mas não é exportado. Em seguida, o builtin usa ,
e /usr/bin/printf
usa .
...
Veja também %g
para arredondamento para um número especificado de dígitos significativos.