Pourquoi la division en arithmétique bash calcule-t-elle la plupart des pourcentages à 0?

15

Tentative d'arithmétique bash pour un script, mais $ene se met pas à jour jusqu'à la fin. La sortie parle d'elle-même.

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

Tl; DR: Affiche 1..5 en pourcentage.

Production :

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

Pourquoi est-ce?

Cybex
la source
1
Connexes: Bash donne uniquement un entier en sortie, quelle que soit l'entrée lors des calculs (bien que, contrairement au problème décrit ici, cela ne puisse pas être résolu correctement en réorganisant les opérations.)
Eliah Kagan

Réponses:

24

bashne peut pas gérer l'arithmétique non entière. Il vous donnera le résultat correct tant que toutes les expressions sont des entiers. Vous devez donc éviter d'obtenir une valeur non entière quelque part dans votre calcul.

Dans votre cas, lorsque vous évaluez 1 / 5, 2 / 5etc., il crée les valeurs zéro entières dans les correspondances bash à certaines valeurs non entières et les résultats sortent à zéro en conséquence. La priorité de la division et de la multiplication sont les mêmes et les mêmes opérateurs précédents sont toujours exécutés de gauche à droite lorsqu'ils sont placés dans l'expression.

Une solution consiste à faire d'abord la multiplication, puis la division afin que bash n'ait jamais à gérer une valeur non entière. L'expression corrigée sera,

$ 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
la source
1
La sous 1/5- expression ne crée pas de valeur non entière . Il crée la valeur entière 0, car le résultat de la division entière de 1 par 5 est 0. D'autres opérations utilisent avec succès cette valeur de 0. Ce n'est pas ce que l'OP avait prévu, mais aucune opération ne crée une valeur non entière et aucune opération échoue.
Eliah Kagan
@EliahKagan merci de nous avoir signalé la faille. Édité.
souravc
16

Bash ne fait pas très bien ce genre d'arithmétique ... Voici votre problème:

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

Si vous devez gérer des valeurs non entières, vous pouvez utiliser 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

(merci à @Arronical d'avoir indiqué comment formater la sortie sous forme d'entiers)

Zanna
la source
Je vais certainement y jeter un œil, merci encore!
Cybex
1
Vous pouvez couper le son .0de la sortie en changeant $percent votre écho en ${percent%.*}:)
Arronical
11

Contrairement à bash, awk propose une arithmétique à virgule flottante complète. Par exemple:

$ 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
la source
5

Essayer

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

:)

Voir la section ÉVALUATION ARITHMÉTIQUE de:

man bash

Il prend uniquement en charge l'entier.

Alfred
la source