Números de pontos flutuantes no bash [duplicado]

1

Eu quero fazer alguma aritmética com bash. Eles dizem para usar a ferramenta certa para o trabalho, e o shell pode não ser a ferramenta certa aqui. Mas é a ferramenta certa para o que estou fazendo de outra forma, e esta é toda a aritmética que precisa ser feita, por favor, tenha paciência comigo.

Digamos que eu tenha uma variável e queira imprimir 5 dígitos significativos depois que eu a multiplicar por 1000. zsh pode fazer isso:

zsh$ x=2.8026407e+00
zsh$ printf "%.5g\n" "$(( 1000*${x} ))"
zsh> 2802.6

O bash também pode fazê-lo?

bash$ x=2.8026407e+00
bash$ printf "%.5g\n" "$(( 1000*${x} ))"
bash> bash: 1000*2.8026407e+00 : syntax error: invalid arithmetic operator (error token is ".8026407e+00 ")

Eu acho que não há como fazer com que o bash nativo entenda as operações de ponto flutuante, não é? Eu sei que posso usar, e. awk , mas eu queria saber se bash poderia fazer isso ou não.

(Não estou surpreso que bash não possa lidar com floats, mas que zsh possa!)

    
por pfnuesel 09.10.2018 / 00:29

4 respostas

3

bash não faz aritmética de ponto flutuante, mas sim ponto fixo, onde o decimal é fixado em zero lugares (ou seja, matemática inteira). Isso significa que você pode contorná-lo para alguma computação muito básica:

$ a=1;b=3
$ echo $(( (a*1000 / b ) ))
333

Então, 1/3 a três lugares é 0,333.

Esta é uma má ideia; não faça isso.

Existem muitas maneiras de fazer matemática FP na linha de comando. Aqui estão apenas dois exemplos:

$ python -c 'print( 1.0 / 3 )'
0.333333333333
$ echo 'scale=3; 1.0/3' | bc
.333
    
por 09.10.2018 / 00:47
2

bash não pode realizar operações matemáticas de ponto decimal, apenas operações inteiras

robert@pip2:/tmp$ echo $((2 * 3))
6
robert@pip2:/tmp$ echo $((2 * 3.5))
bash: 2 * 3.5: syntax error: invalid arithmetic operator (error token is ".5")
robert@pip2:/tmp$ 
    
por 09.10.2018 / 00:41
2

Sim, como dash, o bash é limitado a matemática aritmética inteira em $((…)) .
De fato, por padrão, todas as shells (padrão POSIX) imprimirão 37 com isto:

$ echo "$((1000/27))"
37

De [POSIX] [1]:

Only signed long integer arithmetic is required.

Você precisa alterar os números um pouco para obter a matemática de ponto flutuante em ksh, zsh e yash (não em jsh, dash, ash, lksh, mksh e bash):

$ echo $((1000/27.0))
37.037037037037037

Mas tenha cuidado com a precedência e precisão do zsh:

$ for sh in ksh yash zsh; do $sh -c 'printf "%20d\n" "$(( 1<<63 - 5))"'; done
  288230376151711744
  288230376151711744
 9223372036854775803

$ for sh in ksh yash zsh; do $sh -c 'printf "%-20s\n" "$((1/10.0))"'; done
0.1                 
0.1                 
0.10000000000000001

Limites de truncamento do zsh inesperados:

$ zsh -c 'echo $((12345678901234567890));echo $((12345678901234567890123))'

zsh:1: number truncated after 19 digits: 12345678901234567890
1234567890123456789

zsh:1: number truncated after 22 digits: 12345678901234567890123
-1363962815083169260

Existe uma solução alternativa no bash usando recursos printf (limitado a ~ 10 dígitos):

$ bash -c 'printf "%.10f\n" "$(( 10**10*  1000/27  ))e-10"'
37.0370370370

Mas por que se preocupar em ter bc disponível:

$ echo '1000/27' | bc -l
37.03703703703703703703

[1]: Apenas aritmética de inteiros longos assinados é necessária.

    
por 09.10.2018 / 07:41
1

Não usando bash especificamente, mas você deve ter bc disponível para você:

# bc doesn't like exponential numbers in the manner provided.  It can be done, but this number is equivalent.
x=2.8026407
printf "1000 * %s" "$x" | bc
    
por 09.10.2018 / 05:42