Por que a divisão na aritmética do bash calcula a maioria das porcentagens como 0?


15

Tentativa bash arithemetic para um script, mas $enão é atualizada até o final. A saída fala por si.

max=5
for e in $(seq 1 1 $max); do 
    percent=$(( $e/$max*100 ))
    echo "echo $e / $max : = $percent"
done

Tl; DR: Exiba 1..5 como uma porcentagem.

Resultado :

echo 1 / 5 : = 0
echo 2 / 5 : = 0
echo 3 / 5 : = 0
echo 4 / 5 : = 0
echo 5 / 5 : = 100

Por que é isso?


11
Relacionado: Bash só dá como saída inteiro independentemente de entrada quando a realização de cálculos (Embora, ao contrário do problema descrito aqui, que não pode ser resolvido bem por reordenação das operações.)
Elias Kagan

Respostas:


24

bashnão pode lidar com aritmética não inteira. Ele fornecerá o resultado correto, desde que todas as expressões sejam números inteiros. Portanto, você precisa evitar obter um valor não inteiro em algum lugar do seu cálculo.

No seu caso, quando você está avaliando 1 / 5, 2 / 5etc. , ele cria os valores inteiros de zero nas correspondências do bash para alguns valores não inteiros e os resultados são zero. A precedência de divisão e multiplicação são os mesmos e os mesmos operadores precedentes são sempre executados da esquerda para a direita à medida que são colocados na expressão.

Uma solução alternativa será fazer a multiplicação primeiro e depois a divisão para que o bash nunca precise lidar com valores não inteiros. A expressão corrigida será,

$ max=5; for e in $(seq 1 1 $max); do percent=$(( $e*100/$max )); echo "echo $e / $max : = $percent"; done
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100

11
A subexpressão 1/5não cria um valor não inteiro . Ele cria o valor inteiro 0, uma vez que o resultado da divisão inteira de 1 por 5 é 0. Outras operações usam com sucesso esse valor de 0. Não é isso que o OP pretendia, mas nenhuma operação cria um valor não inteiro e nenhuma operação falha.
Eliah Kagan 9/09/19

@EliahKagan, obrigado por apontar a falha. Editado.
souravc 10/09/19

16

O Bash não se sai muito bem nesse tipo de aritmética ... Aqui está o seu problema:

$ echo $((1/5))
0
$ echo $((2/5))
0
$ echo $((4/5))
0
$ echo $((4/5))
0

Se você precisar manipular valores não inteiros, poderá usar bc

$ max=5; for e in $(seq 1 1 "$max"); do percent=$(bc <<< "scale=1 ; $e/$max*100") ; echo "echo $e / $max : = ${percent%.*}"; done
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100

(obrigado a @Arronical por indicar como formatar a saída como números inteiros)


Definitivamente vou dar uma olhada nisso, obrigado mais uma vez!
Cybex

11
Você pode cortar a .0partir da saída mudando $percent em sua eco para ${percent%.*}:)
Arronical

11

Diferentemente do bash, o awk oferece aritmética de ponto flutuante completo. Por exemplo:

$ awk -v max=5 'BEGIN{for (e=1;e<=max;e++) print "echo " e " / " max " : = " 100*e/max}' 
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100

5

Experimentar

percent=$(( $e*100/$max ))

:)

Veja a seção AVALIAÇÃO ARITMÉTICA de:

man bash

Ele suporta apenas números inteiros.

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.