Como arredondar números de ponto flutuante no shell?


13

Como arredondar corretamente os números de ponto flutuante IEEE 754 na linha de comando?

Quero especificar a precisão do número de saída - a contagem de dígitos fracionários.

O arredondamento 6.66para a precisão 1deve fornecer 6.7, por exemplo. Mais na tabela abaixo:

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

Deve ser utilizável em um shell interativo, mas idealmente robusto o suficiente para usá-lo em scripts de shell de produção.


Eu usaria awk.
Isomorphismes

Respostas:


30

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 printfpode 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 xpara pprecisã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 xentrada padrão e pcomo 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/printfvez do shell interno bashou zshpara solucionar pequenas inconsistências na implementação das printfvariantes, 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/printfusa ....


Consulte também %gpara arredondar para um número especificado de dígitos significativos.


0

A seguir funcionou para mim.

#!/bin/bash
function float() {
bc << EOF
num = $1;
base = num / 1;
if (((num - base) * 10) > 1 )
    base += 1;
print base;
EOF
echo ""
}

float 3.2
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.