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?

Cybex
fonte
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
souravc
fonte
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)

Zanna
fonte
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
John1024
fonte
5

Experimentar

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

:)

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

man bash

Ele suporta apenas números inteiros.

Alfred
fonte