Como arredondar números de ponto flutuante no shell?

7

Como faço para arredondar corretamente números de ponto flutuante IEEE 754 na linha de comando?

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

Arredondar 6.66 para precisão 1 deve dar 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.

    
por Volker Siegel 10.11.2014 / 06:49

2 respostas

21

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.

    
por 10.11.2014 / 06:50
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
    
por 02.09.2018 / 03:29