Arredondando números de ponto flutuante
O que significa "arredondar um número de ponto flutuante"?
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:
Arredondando para cima?
Arredondando para baixo?
Arredondando para zero?
Arredondar para o mais próximo - vincular ao par?
Arredondando para o mais próximo - está longe de zero?
Como lidar com os casos de canto? Como descobrir quais são os casos de canto?
OK, parece que é melhor usarmos uma implementação do padrão IEEE 754 e deixar nosso sistema cuidar disso.
Para arredondar um número de ponto flutuante no shell, com base na aritmética padrão de ponto flutuante, 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.
- Arredonde o número do ponto flutuante usando a implementação normal do IEEE 754.
- Formate o número como uma sequência de saída.
Acontece que o comando shell printf
pode fazer tudo isso. 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 da maneira padrão, se necessário para o formato de saída:
O comando
Arredonde x
para p
precisão de 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
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 o local! Ele especifica o separador entre a parte integral e a fração - o .
, como você pode esperar.
Mas veja o que acontece em um código de idioma 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írgulas seis seis sete. Isso atrapalharia o seu script, com certeza.
(Mas apenas para os dois clientes na Alemanha. Exceto pelas máquinas do desenvolvedor atualmente depurando para 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, em /usr/bin/printf
vez do shell interno bash
ou zsh
para solucionar pequenas inconsistências na implementação das printf
variantes, e evita um efeito muito sujo quando, em um código de idioma alemão, LC_ALL
é definido, mas não exportado. Então, o builtin usa ,
, e /usr/bin/printf
usa .
...
Consulte também %g
para arredondar para um número especificado de dígitos significativos.
awk
.